1use std::collections::{BTreeSet, HashMap, VecDeque};
2
3use anyhow::{anyhow, bail, Result as AnyResult};
4use schemars::JsonSchema;
5
6use cosmwasm_std::{
7 coin, ensure, ensure_eq, to_binary, Addr, AllDelegationsResponse, AllValidatorsResponse, Api,
8 BankMsg, Binary, BlockInfo, BondedDenomResponse, Coin, CustomQuery, Decimal, Delegation,
9 DelegationResponse, DistributionMsg, Empty, Event, FullDelegation, Querier, StakingMsg,
10 StakingQuery, Storage, Timestamp, Uint128, Validator, ValidatorResponse,
11};
12use cw_storage_plus::{Deque, Item, Map};
13use serde::{Deserialize, Serialize};
14
15use crate::app::CosmosRouter;
16use crate::executor::AppResponse;
17use crate::prefixed_storage::{prefixed, prefixed_read};
18use crate::{BankSudo, Module};
19
20#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
22pub struct StakingInfo {
23 pub bonded_denom: String,
25 pub unbonding_time: u64,
27 pub apr: Decimal,
29}
30
31impl Default for StakingInfo {
32 fn default() -> Self {
33 StakingInfo {
34 bonded_denom: "TOKEN".to_string(),
35 unbonding_time: 60,
36 apr: Decimal::percent(10),
37 }
38 }
39}
40
41#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, JsonSchema)]
43struct Shares {
44 stake: Decimal,
45 rewards: Decimal,
46}
47
48impl Shares {
49 pub fn share_of_rewards(&self, validator: &ValidatorInfo, rewards: Decimal) -> Decimal {
51 if validator.stake.is_zero() {
52 return Decimal::zero();
53 }
54 rewards * self.stake / validator.stake
55 }
56}
57
58#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
60struct ValidatorInfo {
61 stakers: BTreeSet<Addr>,
64 stake: Uint128,
66 last_rewards_calculation: Timestamp,
68}
69
70impl ValidatorInfo {
71 pub fn new(block_time: Timestamp) -> Self {
72 Self {
73 stakers: BTreeSet::new(),
74 stake: Uint128::zero(),
75 last_rewards_calculation: block_time,
76 }
77 }
78}
79
80#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
81struct Unbonding {
82 pub delegator: Addr,
83 pub validator: Addr,
84 pub amount: Uint128,
85 pub payout_at: Timestamp,
86}
87
88const STAKING_INFO: Item<StakingInfo> = Item::new("staking_info");
89const STAKES: Map<(&Addr, &Addr), Shares> = Map::new("stakes");
91const VALIDATOR_MAP: Map<&Addr, Validator> = Map::new("validator_map");
92const VALIDATORS: Deque<Validator> = Deque::new("validators");
94const VALIDATOR_INFO: Map<&Addr, ValidatorInfo> = Map::new("validator_info");
96const UNBONDING_QUEUE: Item<VecDeque<Unbonding>> = Item::new("unbonding_queue");
98const WITHDRAW_ADDRESS: Map<&Addr, Addr> = Map::new("withdraw_address");
102
103pub const NAMESPACE_STAKING: &[u8] = b"staking";
104pub const NAMESPACE_DISTRIBUTION: &[u8] = b"distribution";
106
107#[derive(Clone, std::fmt::Debug, PartialEq, Eq, JsonSchema)]
109pub enum StakingSudo {
110 Slash {
113 validator: String,
114 percentage: Decimal,
115 },
116 ProcessQueue {},
120}
121
122pub trait Staking: Module<ExecT = StakingMsg, QueryT = StakingQuery, SudoT = StakingSudo> {}
123
124pub trait Distribution: Module<ExecT = DistributionMsg, QueryT = Empty, SudoT = Empty> {}
125
126pub struct StakeKeeper {
127 module_addr: Addr,
128}
129
130impl Default for StakeKeeper {
131 fn default() -> Self {
132 Self::new()
133 }
134}
135
136impl StakeKeeper {
137 pub fn new() -> Self {
138 StakeKeeper {
139 module_addr: Addr::unchecked("staking_module"),
141 }
142 }
143
144 pub fn setup(&self, storage: &mut dyn Storage, staking_info: StakingInfo) -> AnyResult<()> {
146 let mut storage = prefixed(storage, NAMESPACE_STAKING);
147
148 STAKING_INFO.save(&mut storage, &staking_info)?;
149 Ok(())
150 }
151
152 pub fn add_validator(
154 &self,
155 api: &dyn Api,
156 storage: &mut dyn Storage,
157 block: &BlockInfo,
158 validator: Validator,
159 ) -> AnyResult<()> {
160 let mut storage = prefixed(storage, NAMESPACE_STAKING);
161
162 let val_addr = api.addr_validate(&validator.address)?;
163 if VALIDATOR_MAP.may_load(&storage, &val_addr)?.is_some() {
164 bail!(
165 "Cannot add validator {}, since a validator with that address already exists",
166 val_addr
167 );
168 }
169
170 VALIDATOR_MAP.save(&mut storage, &val_addr, &validator)?;
171 VALIDATORS.push_back(&mut storage, &validator)?;
172 VALIDATOR_INFO.save(&mut storage, &val_addr, &ValidatorInfo::new(block.time))?;
173 Ok(())
174 }
175
176 fn get_staking_info(staking_storage: &dyn Storage) -> AnyResult<StakingInfo> {
177 Ok(STAKING_INFO.may_load(staking_storage)?.unwrap_or_default())
178 }
179
180 pub fn get_rewards(
182 &self,
183 storage: &dyn Storage,
184 block: &BlockInfo,
185 delegator: &Addr,
186 validator: &Addr,
187 ) -> AnyResult<Option<Coin>> {
188 let staking_storage = prefixed_read(storage, NAMESPACE_STAKING);
189
190 let validator_obj = match self.get_validator(&staking_storage, validator)? {
191 Some(validator) => validator,
192 None => bail!("validator {} not found", validator),
193 };
194 let shares = match STAKES.load(&staking_storage, (delegator, validator)) {
196 Ok(stakes) => stakes,
197 Err(_) => {
198 return Ok(None);
199 }
200 };
201 let validator_info = VALIDATOR_INFO.load(&staking_storage, validator)?;
202
203 Self::get_rewards_internal(
204 &staking_storage,
205 block,
206 &shares,
207 &validator_obj,
208 &validator_info,
209 )
210 .map(Some)
211 }
212
213 fn get_rewards_internal(
214 staking_storage: &dyn Storage,
215 block: &BlockInfo,
216 shares: &Shares,
217 validator: &Validator,
218 validator_info: &ValidatorInfo,
219 ) -> AnyResult<Coin> {
220 let staking_info = Self::get_staking_info(staking_storage)?;
221
222 let new_validator_rewards = Self::calculate_rewards(
224 block.time,
225 validator_info.last_rewards_calculation,
226 staking_info.apr,
227 validator.commission,
228 validator_info.stake,
229 );
230
231 let delegator_rewards =
233 shares.rewards + shares.share_of_rewards(validator_info, new_validator_rewards);
234
235 Ok(Coin {
236 denom: staking_info.bonded_denom,
237 amount: Uint128::new(1) * delegator_rewards, })
239 }
240
241 fn calculate_rewards(
243 current_time: Timestamp,
244 since: Timestamp,
245 interest_rate: Decimal,
246 validator_commission: Decimal,
247 stake: Uint128,
248 ) -> Decimal {
249 let time_diff = current_time.minus_seconds(since.seconds()).seconds();
251
252 let reward = Decimal::from_ratio(stake, 1u128)
254 * interest_rate
255 * Decimal::from_ratio(time_diff, 1u128)
256 / Decimal::from_ratio(60u128 * 60 * 24 * 365, 1u128);
257 let commission = reward * validator_commission;
258
259 reward - commission
260 }
261
262 fn update_rewards(
266 api: &dyn Api,
267 staking_storage: &mut dyn Storage,
268 block: &BlockInfo,
269 validator: &Addr,
270 ) -> AnyResult<()> {
271 let staking_info = Self::get_staking_info(staking_storage)?;
272
273 let mut validator_info = VALIDATOR_INFO
274 .may_load(staking_storage, validator)?
275 .ok_or_else(|| anyhow!("validator {} not found", validator))?;
276
277 let validator_obj = VALIDATOR_MAP.load(staking_storage, validator)?;
278
279 if validator_info.last_rewards_calculation >= block.time {
280 return Ok(());
281 }
282
283 let new_rewards = Self::calculate_rewards(
284 block.time,
285 validator_info.last_rewards_calculation,
286 staking_info.apr,
287 validator_obj.commission,
288 validator_info.stake,
289 );
290
291 if !new_rewards.is_zero() {
293 validator_info.last_rewards_calculation = block.time;
294
295 VALIDATOR_INFO.save(staking_storage, validator, &validator_info)?;
297
298 let validator_addr = api.addr_validate(&validator_obj.address)?;
299 for staker in validator_info.stakers.iter() {
301 STAKES.update(
302 staking_storage,
303 (staker, &validator_addr),
304 |shares| -> AnyResult<_> {
305 let mut shares =
306 shares.expect("all stakers in validator_info should exist");
307 shares.rewards += shares.share_of_rewards(&validator_info, new_rewards);
308 Ok(shares)
309 },
310 )?;
311 }
312 }
313 Ok(())
314 }
315
316 fn get_validator(
318 &self,
319 staking_storage: &dyn Storage,
320 address: &Addr,
321 ) -> AnyResult<Option<Validator>> {
322 Ok(VALIDATOR_MAP.may_load(staking_storage, address)?)
323 }
324
325 fn get_validators(&self, staking_storage: &dyn Storage) -> AnyResult<Vec<Validator>> {
327 let res: Result<_, _> = VALIDATORS.iter(staking_storage)?.collect();
328 Ok(res?)
329 }
330
331 fn get_stake(
332 &self,
333 staking_storage: &dyn Storage,
334 account: &Addr,
335 validator: &Addr,
336 ) -> AnyResult<Option<Coin>> {
337 let shares = STAKES.may_load(staking_storage, (account, validator))?;
338 let staking_info = Self::get_staking_info(staking_storage)?;
339
340 Ok(shares.map(|shares| {
341 Coin {
342 denom: staking_info.bonded_denom,
343 amount: Uint128::new(1) * shares.stake, }
345 }))
346 }
347
348 fn add_stake(
349 &self,
350 api: &dyn Api,
351 staking_storage: &mut dyn Storage,
352 block: &BlockInfo,
353 to_address: &Addr,
354 validator: &Addr,
355 amount: Coin,
356 ) -> AnyResult<()> {
357 self.validate_denom(staking_storage, &amount)?;
358 self.update_stake(
359 api,
360 staking_storage,
361 block,
362 to_address,
363 validator,
364 amount.amount,
365 false,
366 )
367 }
368
369 fn remove_stake(
370 &self,
371 api: &dyn Api,
372 staking_storage: &mut dyn Storage,
373 block: &BlockInfo,
374 from_address: &Addr,
375 validator: &Addr,
376 amount: Coin,
377 ) -> AnyResult<()> {
378 self.validate_denom(staking_storage, &amount)?;
379 self.update_stake(
380 api,
381 staking_storage,
382 block,
383 from_address,
384 validator,
385 amount.amount,
386 true,
387 )
388 }
389
390 fn update_stake(
391 &self,
392 api: &dyn Api,
393 staking_storage: &mut dyn Storage,
394 block: &BlockInfo,
395 delegator: &Addr,
396 validator: &Addr,
397 amount: impl Into<Uint128>,
398 sub: bool,
399 ) -> AnyResult<()> {
400 let amount = amount.into();
401
402 Self::update_rewards(api, staking_storage, block, validator)?;
404
405 let mut validator_info = VALIDATOR_INFO
407 .may_load(staking_storage, validator)?
408 .unwrap_or_else(|| ValidatorInfo::new(block.time));
409 let mut shares = STAKES
410 .may_load(staking_storage, (delegator, validator))?
411 .unwrap_or_default();
412 let amount_dec = Decimal::from_ratio(amount, 1u128);
413 if sub {
414 if amount_dec > shares.stake {
415 bail!("insufficient stake");
416 }
417 shares.stake -= amount_dec;
418 validator_info.stake = validator_info.stake.checked_sub(amount)?;
419 } else {
420 shares.stake += amount_dec;
421 validator_info.stake = validator_info.stake.checked_add(amount)?;
422 }
423
424 let unbonding = UNBONDING_QUEUE
426 .may_load(staking_storage)?
427 .unwrap_or_default()
428 .into_iter()
429 .filter(|ub| &ub.delegator == delegator && &ub.validator == validator)
430 .map(|ub| ub.amount)
431 .sum::<Uint128>();
432
433 if shares.stake.is_zero() && unbonding.is_zero() {
435 STAKES.remove(staking_storage, (delegator, validator));
437 validator_info.stakers.remove(delegator);
438 } else {
439 STAKES.save(staking_storage, (delegator, validator), &shares)?;
440 validator_info.stakers.insert(delegator.clone());
441 }
442 VALIDATOR_INFO.save(staking_storage, validator, &validator_info)?;
444
445 Ok(())
446 }
447
448 fn slash(
449 &self,
450 api: &dyn Api,
451 staking_storage: &mut dyn Storage,
452 block: &BlockInfo,
453 validator: &Addr,
454 percentage: Decimal,
455 ) -> AnyResult<()> {
456 Self::update_rewards(api, staking_storage, block, validator)?;
458
459 let mut validator_info = VALIDATOR_INFO
461 .may_load(staking_storage, validator)?
462 .ok_or_else(|| anyhow!("validator {} not found", validator))?;
463
464 let remaining_percentage = Decimal::one() - percentage;
465 validator_info.stake = validator_info.stake * remaining_percentage;
466
467 if validator_info.stake.is_zero() {
469 for delegator in validator_info.stakers.iter() {
471 STAKES.remove(staking_storage, (delegator, validator));
472 }
473 validator_info.stakers.clear();
474 } else {
475 for delegator in validator_info.stakers.iter() {
477 STAKES.update(
478 staking_storage,
479 (delegator, validator),
480 |stake| -> AnyResult<_> {
481 let mut stake = stake.expect("all stakers in validator_info should exist");
482 stake.stake *= remaining_percentage;
483
484 Ok(stake)
485 },
486 )?;
487 }
488 }
489 let mut unbonding_queue = UNBONDING_QUEUE
491 .may_load(staking_storage)?
492 .unwrap_or_default();
493 unbonding_queue
494 .iter_mut()
495 .filter(|ub| &ub.validator == validator)
496 .for_each(|mut ub| {
497 ub.amount = ub.amount * remaining_percentage;
498 });
499 UNBONDING_QUEUE.save(staking_storage, &unbonding_queue)?;
500
501 VALIDATOR_INFO.save(staking_storage, validator, &validator_info)?;
502 Ok(())
503 }
504
505 fn validate_denom(&self, staking_storage: &dyn Storage, amount: &Coin) -> AnyResult<()> {
507 let staking_info = Self::get_staking_info(staking_storage)?;
508 ensure_eq!(
509 amount.denom,
510 staking_info.bonded_denom,
511 anyhow!(
512 "cannot delegate coins of denominator {}, only of {}",
513 amount.denom,
514 staking_info.bonded_denom
515 )
516 );
517 Ok(())
518 }
519
520 fn validate_percentage(&self, percentage: Decimal) -> AnyResult<()> {
522 ensure!(percentage <= Decimal::one(), anyhow!("expected percentage"));
523 Ok(())
524 }
525}
526
527impl Staking for StakeKeeper {}
528
529impl Module for StakeKeeper {
530 type ExecT = StakingMsg;
531 type QueryT = StakingQuery;
532 type SudoT = StakingSudo;
533
534 fn execute<ExecC, QueryC: CustomQuery>(
535 &self,
536 api: &dyn Api,
537 storage: &mut dyn Storage,
538 router: &dyn CosmosRouter<ExecC = ExecC, QueryC = QueryC>,
539 block: &BlockInfo,
540 sender: Addr,
541 msg: StakingMsg,
542 ) -> AnyResult<AppResponse> {
543 let mut staking_storage = prefixed(storage, NAMESPACE_STAKING);
544 match msg {
545 StakingMsg::Delegate { validator, amount } => {
546 let validator = api.addr_validate(&validator)?;
547
548 let events = vec![Event::new("delegate")
550 .add_attribute("validator", &validator)
551 .add_attribute("amount", format!("{}{}", amount.amount, amount.denom))
552 .add_attribute("new_shares", amount.amount.to_string())]; self.add_stake(
554 api,
555 &mut staking_storage,
556 block,
557 &sender,
558 &validator,
559 amount.clone(),
560 )?;
561 if !amount.amount.is_zero() {
563 router.execute(
564 api,
565 storage,
566 block,
567 sender,
568 BankMsg::Send {
569 to_address: self.module_addr.to_string(),
570 amount: vec![amount],
571 }
572 .into(),
573 )?;
574 }
575 Ok(AppResponse { events, data: None })
576 }
577 StakingMsg::Undelegate { validator, amount } => {
578 let validator = api.addr_validate(&validator)?;
579 self.validate_denom(&staking_storage, &amount)?;
580
581 let events = vec![Event::new("unbond")
583 .add_attribute("validator", &validator)
584 .add_attribute("amount", format!("{}{}", amount.amount, amount.denom))
585 .add_attribute("completion_time", "2022-09-27T14:00:00+00:00")]; self.remove_stake(
587 api,
588 &mut staking_storage,
589 block,
590 &sender,
591 &validator,
592 amount.clone(),
593 )?;
594 let staking_info = Self::get_staking_info(&staking_storage)?;
596 let mut unbonding_queue = UNBONDING_QUEUE
597 .may_load(&staking_storage)?
598 .unwrap_or_default();
599 unbonding_queue.push_back(Unbonding {
600 delegator: sender.clone(),
601 validator,
602 amount: amount.amount,
603 payout_at: block.time.plus_seconds(staking_info.unbonding_time),
604 });
605 UNBONDING_QUEUE.save(&mut staking_storage, &unbonding_queue)?;
606 Ok(AppResponse { events, data: None })
607 }
608 StakingMsg::Redelegate {
609 src_validator,
610 dst_validator,
611 amount,
612 } => {
613 let src_validator = api.addr_validate(&src_validator)?;
614 let dst_validator = api.addr_validate(&dst_validator)?;
615 let events = vec![Event::new("redelegate")
617 .add_attribute("source_validator", &src_validator)
618 .add_attribute("destination_validator", &dst_validator)
619 .add_attribute("amount", format!("{}{}", amount.amount, amount.denom))];
620
621 self.remove_stake(
622 api,
623 &mut staking_storage,
624 block,
625 &sender,
626 &src_validator,
627 amount.clone(),
628 )?;
629 self.add_stake(
630 api,
631 &mut staking_storage,
632 block,
633 &sender,
634 &dst_validator,
635 amount,
636 )?;
637
638 Ok(AppResponse { events, data: None })
639 }
640 m => bail!("Unsupported staking message: {:?}", m),
641 }
642 }
643
644 fn sudo<ExecC, QueryC: CustomQuery>(
645 &self,
646 api: &dyn Api,
647 storage: &mut dyn Storage,
648 router: &dyn CosmosRouter<ExecC = ExecC, QueryC = QueryC>,
649 block: &BlockInfo,
650 msg: StakingSudo,
651 ) -> AnyResult<AppResponse> {
652 match msg {
653 StakingSudo::Slash {
654 validator,
655 percentage,
656 } => {
657 let mut staking_storage = prefixed(storage, NAMESPACE_STAKING);
658 let validator = api.addr_validate(&validator)?;
659 self.validate_percentage(percentage)?;
660
661 self.slash(api, &mut staking_storage, block, &validator, percentage)?;
662
663 Ok(AppResponse::default())
664 }
665 StakingSudo::ProcessQueue {} => {
666 let staking_storage = prefixed_read(storage, NAMESPACE_STAKING);
667 let mut unbonding_queue = UNBONDING_QUEUE
668 .may_load(&staking_storage)?
669 .unwrap_or_default();
670 loop {
671 let mut staking_storage = prefixed(storage, NAMESPACE_STAKING);
672 match unbonding_queue.front() {
673 Some(Unbonding { payout_at, .. }) if payout_at <= &block.time => {
675 let Unbonding {
677 delegator,
678 validator,
679 amount,
680 ..
681 } = unbonding_queue.pop_front().unwrap();
682
683 let delegation = self
685 .get_stake(&staking_storage, &delegator, &validator)?
686 .map(|mut stake| {
687 stake.amount += unbonding_queue
689 .iter()
690 .filter(|u| {
691 u.delegator == delegator && u.validator == validator
692 })
693 .map(|u| u.amount)
694 .sum::<Uint128>();
695 stake
696 });
697 match delegation {
698 Some(delegation) if delegation.amount.is_zero() => {
699 STAKES.remove(&mut staking_storage, (&delegator, &validator));
700 }
701 None => {
702 STAKES.remove(&mut staking_storage, (&delegator, &validator))
703 }
704 _ => {}
705 }
706
707 let staking_info = Self::get_staking_info(&staking_storage)?;
708 if !amount.is_zero() {
709 router.execute(
710 api,
711 storage,
712 block,
713 self.module_addr.clone(),
714 BankMsg::Send {
715 to_address: delegator.into_string(),
716 amount: vec![coin(
717 amount.u128(),
718 &staking_info.bonded_denom,
719 )],
720 }
721 .into(),
722 )?;
723 }
724 }
725 _ => break,
726 }
727 }
728 let mut staking_storage = prefixed(storage, NAMESPACE_STAKING);
729 UNBONDING_QUEUE.save(&mut staking_storage, &unbonding_queue)?;
730 Ok(AppResponse::default())
731 }
732 }
733 }
734
735 fn query(
736 &self,
737 api: &dyn Api,
738 storage: &dyn Storage,
739 _querier: &dyn Querier,
740 block: &BlockInfo,
741 request: StakingQuery,
742 ) -> AnyResult<Binary> {
743 let staking_storage = prefixed_read(storage, NAMESPACE_STAKING);
744 match request {
745 StakingQuery::BondedDenom {} => Ok(to_binary(&BondedDenomResponse {
746 denom: Self::get_staking_info(&staking_storage)?.bonded_denom,
747 })?),
748 StakingQuery::AllDelegations { delegator } => {
749 let delegator = api.addr_validate(&delegator)?;
750 let validators = self.get_validators(&staking_storage)?;
751
752 let mut unbondings = HashMap::new();
753 for ub in UNBONDING_QUEUE
754 .may_load(&staking_storage)?
755 .unwrap_or_default()
756 .into_iter()
757 .filter(|ub| ub.delegator == delegator)
758 {
759 *unbondings.entry(ub.validator.into_string()).or_default() += ub.amount;
760 }
761
762 let res: AnyResult<Vec<Delegation>> = validators
763 .into_iter()
764 .filter_map(|validator| {
765 let delegator = delegator.clone();
766 let amount = self
767 .get_stake(
768 &staking_storage,
769 &delegator,
770 &Addr::unchecked(&validator.address),
771 )
772 .transpose()?;
773
774 Some(amount.map(|mut amount| {
775 amount.amount += unbondings
777 .get(&validator.address)
778 .copied()
779 .unwrap_or(Uint128::zero());
780 Delegation {
781 delegator,
782 validator: validator.address,
783 amount,
784 }
785 }))
786 })
787 .collect();
788
789 Ok(to_binary(&AllDelegationsResponse { delegations: res? })?)
790 }
791 StakingQuery::Delegation {
792 delegator,
793 validator,
794 } => {
795 let validator_addr = Addr::unchecked(&validator);
796 let validator_obj = match self.get_validator(&staking_storage, &validator_addr)? {
797 Some(validator) => validator,
798 None => bail!("non-existent validator {}", validator),
799 };
800 let delegator = api.addr_validate(&delegator)?;
801
802 let shares = STAKES
803 .may_load(&staking_storage, (&delegator, &validator_addr))?
804 .unwrap_or_default();
805
806 let validator_info = VALIDATOR_INFO.load(&staking_storage, &validator_addr)?;
807 let reward = Self::get_rewards_internal(
808 &staking_storage,
809 block,
810 &shares,
811 &validator_obj,
812 &validator_info,
813 )?;
814 let staking_info = Self::get_staking_info(&staking_storage)?;
815
816 let unbonding_amounts: Uint128 = UNBONDING_QUEUE
818 .may_load(&staking_storage)?
819 .unwrap_or_default()
820 .into_iter()
821 .filter(|ub| ub.delegator == delegator && ub.validator == validator)
822 .map(|ub| ub.amount)
823 .sum();
824
825 let amount = coin(
826 (shares.stake * Uint128::new(1))
827 .checked_add(unbonding_amounts)?
828 .u128(),
829 staking_info.bonded_denom,
830 );
831
832 let full_delegation_response = if amount.amount.is_zero() {
833 DelegationResponse { delegation: None }
835 } else {
836 DelegationResponse {
837 delegation: Some(FullDelegation {
838 delegator,
839 validator,
840 amount: amount.clone(),
841 can_redelegate: amount, accumulated_rewards: if reward.amount.is_zero() {
843 vec![]
844 } else {
845 vec![reward]
846 },
847 }),
848 }
849 };
850
851 let res = to_binary(&full_delegation_response)?;
852 Ok(res)
853 }
854 StakingQuery::AllValidators {} => Ok(to_binary(&AllValidatorsResponse {
855 validators: self.get_validators(&staking_storage)?,
856 })?),
857 StakingQuery::Validator { address } => Ok(to_binary(&ValidatorResponse {
858 validator: self.get_validator(&staking_storage, &Addr::unchecked(address))?,
859 })?),
860 q => bail!("Unsupported staking sudo message: {:?}", q),
861 }
862 }
863}
864
865#[derive(Default)]
866pub struct DistributionKeeper {}
867
868impl DistributionKeeper {
869 pub fn new() -> Self {
870 DistributionKeeper {}
871 }
872
873 pub fn remove_rewards(
875 &self,
876 api: &dyn Api,
877 storage: &mut dyn Storage,
878 block: &BlockInfo,
879 delegator: &Addr,
880 validator: &Addr,
881 ) -> AnyResult<Uint128> {
882 let mut staking_storage = prefixed(storage, NAMESPACE_STAKING);
883 StakeKeeper::update_rewards(api, &mut staking_storage, block, validator)?;
885
886 let mut shares = STAKES.load(&staking_storage, (delegator, validator))?;
888 let rewards = Uint128::new(1) * shares.rewards; shares.rewards = Decimal::zero();
892 STAKES.save(&mut staking_storage, (delegator, validator), &shares)?;
893
894 Ok(rewards)
895 }
896
897 pub fn get_withdraw_address(storage: &dyn Storage, delegator: &Addr) -> AnyResult<Addr> {
898 Ok(match WITHDRAW_ADDRESS.may_load(storage, delegator)? {
899 Some(a) => a,
900 None => delegator.clone(),
901 })
902 }
903
904 pub fn set_withdraw_address(
906 storage: &mut dyn Storage,
907 delegator: &Addr,
908 withdraw_address: &Addr,
909 ) -> AnyResult<()> {
910 if delegator == withdraw_address {
911 WITHDRAW_ADDRESS.remove(storage, delegator);
912 Ok(())
913 } else {
914 WITHDRAW_ADDRESS
917 .save(storage, delegator, withdraw_address)
918 .map_err(|e| e.into())
919 }
920 }
921}
922
923impl Distribution for DistributionKeeper {}
924
925impl Module for DistributionKeeper {
926 type ExecT = DistributionMsg;
927 type QueryT = Empty;
928 type SudoT = Empty;
929
930 fn execute<ExecC, QueryC: CustomQuery>(
931 &self,
932 api: &dyn Api,
933 storage: &mut dyn Storage,
934 router: &dyn CosmosRouter<ExecC = ExecC, QueryC = QueryC>,
935 block: &BlockInfo,
936 sender: Addr,
937 msg: DistributionMsg,
938 ) -> AnyResult<AppResponse> {
939 match msg {
940 DistributionMsg::WithdrawDelegatorReward { validator } => {
941 let validator_addr = api.addr_validate(&validator)?;
942
943 let rewards = self.remove_rewards(api, storage, block, &sender, &validator_addr)?;
944
945 let staking_storage = prefixed_read(storage, NAMESPACE_STAKING);
946 let distribution_storage = prefixed_read(storage, NAMESPACE_DISTRIBUTION);
947 let staking_info = StakeKeeper::get_staking_info(&staking_storage)?;
948 let receiver = Self::get_withdraw_address(&distribution_storage, &sender)?;
949 router.sudo(
951 api,
952 storage,
953 block,
954 BankSudo::Mint {
955 to_address: receiver.into_string(),
956 amount: vec![Coin {
957 amount: rewards,
958 denom: staking_info.bonded_denom.clone(),
959 }],
960 }
961 .into(),
962 )?;
963
964 let events = vec![Event::new("withdraw_delegator_reward")
965 .add_attribute("validator", &validator)
966 .add_attribute("sender", &sender)
967 .add_attribute(
968 "amount",
969 format!("{}{}", rewards, staking_info.bonded_denom),
970 )];
971 Ok(AppResponse { events, data: None })
972 }
973 DistributionMsg::SetWithdrawAddress { address } => {
974 let address = api.addr_validate(&address)?;
975 let storage = &mut prefixed(storage, NAMESPACE_DISTRIBUTION);
977 Self::set_withdraw_address(storage, &sender, &address)?;
978 Ok(AppResponse {
979 data: None,
980 events: vec![Event::new("set_withdraw_address")
982 .add_attribute("withdraw_address", address)],
983 })
984 }
985 m => bail!("Unsupported distribution message: {:?}", m),
986 }
987 }
988
989 fn sudo<ExecC, QueryC>(
990 &self,
991 _api: &dyn Api,
992 _storage: &mut dyn Storage,
993 _router: &dyn CosmosRouter<ExecC = ExecC, QueryC = QueryC>,
994 _block: &BlockInfo,
995 _msg: Empty,
996 ) -> AnyResult<AppResponse> {
997 bail!("Something went wrong - Distribution doesn't have sudo messages")
998 }
999
1000 fn query(
1001 &self,
1002 _api: &dyn Api,
1003 _storage: &dyn Storage,
1004 _querier: &dyn Querier,
1005 _block: &BlockInfo,
1006 _request: Empty,
1007 ) -> AnyResult<Binary> {
1008 bail!("Something went wrong - Distribution doesn't have query messages")
1009 }
1010}
1011
1012#[cfg(test)]
1013mod test {
1014 use crate::{
1015 app::MockRouter, stargate::StargateKeeper, BankKeeper, FailingModule, Router, WasmKeeper,
1016 };
1017
1018 use super::*;
1019
1020 use cosmwasm_std::{
1021 from_slice,
1022 testing::{mock_env, MockApi, MockStorage},
1023 BalanceResponse, BankQuery, GovMsg, IbcMsg, IbcQuery,
1024 };
1025
1026 type BasicRouter<ExecC = Empty, QueryC = Empty> = Router<
1028 BankKeeper,
1029 FailingModule<ExecC, QueryC, Empty>,
1030 WasmKeeper<ExecC, QueryC>,
1031 StakeKeeper,
1032 DistributionKeeper,
1033 FailingModule<IbcMsg, IbcQuery, Empty>,
1034 FailingModule<GovMsg, Empty, Empty>,
1035 StargateKeeper<ExecC, QueryC>,
1036 >;
1037
1038 fn mock_router() -> BasicRouter {
1039 Router {
1040 wasm: WasmKeeper::new(),
1041 bank: BankKeeper::new(),
1042 custom: FailingModule::new(),
1043 staking: StakeKeeper::new(),
1044 distribution: DistributionKeeper::new(),
1045 ibc: FailingModule::new(),
1046 gov: FailingModule::new(),
1047 stargate: StargateKeeper::new(),
1048 }
1049 }
1050
1051 fn setup_test_env(
1052 apr: Decimal,
1053 validator_commission: Decimal,
1054 ) -> (MockApi, MockStorage, BasicRouter, BlockInfo, Addr) {
1055 let api = MockApi::default();
1056 let router = mock_router();
1057 let mut store = MockStorage::new();
1058 let block = mock_env().block;
1059
1060 let validator = api.addr_validate("testvaloper1").unwrap();
1061
1062 router
1063 .staking
1064 .setup(
1065 &mut store,
1066 StakingInfo {
1067 bonded_denom: "TOKEN".to_string(),
1068 unbonding_time: 60,
1069 apr,
1070 },
1071 )
1072 .unwrap();
1073
1074 let valoper1 = Validator {
1076 address: "testvaloper1".to_string(),
1077 commission: validator_commission,
1078 max_commission: Decimal::percent(100),
1079 max_change_rate: Decimal::percent(1),
1080 };
1081 router
1082 .staking
1083 .add_validator(&api, &mut store, &block, valoper1)
1084 .unwrap();
1085
1086 (api, store, router, block, validator)
1087 }
1088
1089 #[test]
1090 fn add_get_validators() {
1091 let api = MockApi::default();
1092 let mut store = MockStorage::new();
1093 let stake = StakeKeeper::default();
1094 let block = mock_env().block;
1095
1096 let valoper1 = Validator {
1098 address: "testvaloper1".to_string(),
1099 commission: Decimal::percent(10),
1100 max_commission: Decimal::percent(20),
1101 max_change_rate: Decimal::percent(1),
1102 };
1103 stake
1104 .add_validator(&api, &mut store, &block, valoper1.clone())
1105 .unwrap();
1106
1107 let staking_storage = prefixed_read(&store, NAMESPACE_STAKING);
1109 let val = stake
1110 .get_validator(
1111 &staking_storage,
1112 &api.addr_validate("testvaloper1").unwrap(),
1113 )
1114 .unwrap()
1115 .unwrap();
1116 assert_eq!(val, valoper1);
1117
1118 let valoper1_fake = Validator {
1120 address: "testvaloper1".to_string(),
1121 commission: Decimal::percent(1),
1122 max_commission: Decimal::percent(10),
1123 max_change_rate: Decimal::percent(100),
1124 };
1125 stake
1126 .add_validator(&api, &mut store, &block, valoper1_fake)
1127 .unwrap_err();
1128
1129 let staking_storage = prefixed_read(&store, NAMESPACE_STAKING);
1131 let val = stake
1132 .get_validator(
1133 &staking_storage,
1134 &api.addr_validate("testvaloper1").unwrap(),
1135 )
1136 .unwrap()
1137 .unwrap();
1138 assert_eq!(val, valoper1);
1139 }
1140
1141 #[test]
1142 fn validator_slashing() {
1143 let api = MockApi::default();
1144 let router = MockRouter::default();
1145 let mut store = MockStorage::new();
1146 let stake = StakeKeeper::new();
1147 let block = mock_env().block;
1148
1149 let delegator = Addr::unchecked("delegator");
1150 let validator = api.addr_validate("testvaloper1").unwrap();
1151
1152 let valoper1 = Validator {
1154 address: "testvaloper1".to_string(),
1155 commission: Decimal::percent(10),
1156 max_commission: Decimal::percent(20),
1157 max_change_rate: Decimal::percent(1),
1158 };
1159 stake
1160 .add_validator(&api, &mut store, &block, valoper1)
1161 .unwrap();
1162
1163 let mut staking_storage = prefixed(&mut store, NAMESPACE_STAKING);
1165 stake
1166 .add_stake(
1167 &api,
1168 &mut staking_storage,
1169 &block,
1170 &delegator,
1171 &validator,
1172 coin(100, "TOKEN"),
1173 )
1174 .unwrap();
1175
1176 stake
1178 .sudo(
1179 &api,
1180 &mut store,
1181 &router,
1182 &block,
1183 StakingSudo::Slash {
1184 validator: "testvaloper1".to_string(),
1185 percentage: Decimal::percent(50),
1186 },
1187 )
1188 .unwrap();
1189
1190 let staking_storage = prefixed(&mut store, NAMESPACE_STAKING);
1192 let stake_left = stake
1193 .get_stake(&staking_storage, &delegator, &validator)
1194 .unwrap();
1195 assert_eq!(
1196 stake_left.unwrap().amount.u128(),
1197 50,
1198 "should have slashed 50%"
1199 );
1200
1201 stake
1203 .sudo(
1204 &api,
1205 &mut store,
1206 &router,
1207 &block,
1208 StakingSudo::Slash {
1209 validator: "testvaloper1".to_string(),
1210 percentage: Decimal::percent(100),
1211 },
1212 )
1213 .unwrap();
1214
1215 let staking_storage = prefixed(&mut store, NAMESPACE_STAKING);
1217 let stake_left = stake
1218 .get_stake(&staking_storage, &delegator, &validator)
1219 .unwrap();
1220 assert_eq!(stake_left, None, "should have slashed whole stake");
1221 }
1222
1223 #[test]
1224 fn rewards_work_for_single_delegator() {
1225 let (api, mut store, router, mut block, validator) =
1226 setup_test_env(Decimal::percent(10), Decimal::percent(10));
1227 let stake = &router.staking;
1228 let distr = &router.distribution;
1229 let delegator = Addr::unchecked("delegator");
1230
1231 let mut staking_storage = prefixed(&mut store, NAMESPACE_STAKING);
1232 stake
1234 .add_stake(
1235 &api,
1236 &mut staking_storage,
1237 &block,
1238 &delegator,
1239 &validator,
1240 coin(200, "TOKEN"),
1241 )
1242 .unwrap();
1243
1244 block.time = block.time.plus_seconds(60 * 60 * 24 * 365 / 2);
1246
1247 let rewards = stake
1249 .get_rewards(&store, &block, &delegator, &validator)
1250 .unwrap()
1251 .unwrap();
1252 assert_eq!(rewards.amount.u128(), 9, "should have 9 tokens reward");
1253
1254 distr
1256 .execute(
1257 &api,
1258 &mut store,
1259 &router,
1260 &block,
1261 delegator.clone(),
1262 DistributionMsg::WithdrawDelegatorReward {
1263 validator: validator.to_string(),
1264 },
1265 )
1266 .unwrap();
1267
1268 let rewards = stake
1270 .get_rewards(&store, &block, &delegator, &validator)
1271 .unwrap()
1272 .unwrap();
1273 assert_eq!(rewards.amount.u128(), 0);
1274
1275 block.time = block.time.plus_seconds(60 * 60 * 24 * 365 / 2);
1277 let rewards = stake
1279 .get_rewards(&store, &block, &delegator, &validator)
1280 .unwrap()
1281 .unwrap();
1282 assert_eq!(rewards.amount.u128(), 9);
1283 }
1284
1285 #[test]
1286 fn rewards_work_for_multiple_delegators() {
1287 let (api, mut store, router, mut block, validator) =
1288 setup_test_env(Decimal::percent(10), Decimal::percent(10));
1289 let stake = &router.staking;
1290 let distr = &router.distribution;
1291 let bank = &router.bank;
1292 let delegator1 = Addr::unchecked("delegator1");
1293 let delegator2 = Addr::unchecked("delegator2");
1294
1295 let mut staking_storage = prefixed(&mut store, NAMESPACE_STAKING);
1296
1297 stake
1299 .add_stake(
1300 &api,
1301 &mut staking_storage,
1302 &block,
1303 &delegator1,
1304 &validator,
1305 coin(100, "TOKEN"),
1306 )
1307 .unwrap();
1308 stake
1309 .add_stake(
1310 &api,
1311 &mut staking_storage,
1312 &block,
1313 &delegator2,
1314 &validator,
1315 coin(200, "TOKEN"),
1316 )
1317 .unwrap();
1318
1319 block.time = block.time.plus_seconds(60 * 60 * 24 * 365);
1321
1322 let rewards = stake
1324 .get_rewards(&store, &block, &delegator1, &validator)
1325 .unwrap()
1326 .unwrap();
1327 assert_eq!(rewards.amount.u128(), 9);
1328
1329 let rewards = stake
1331 .get_rewards(&store, &block, &delegator2, &validator)
1332 .unwrap()
1333 .unwrap();
1334 assert_eq!(rewards.amount.u128(), 18);
1335
1336 let mut staking_storage = prefixed(&mut store, NAMESPACE_STAKING);
1338 stake
1339 .add_stake(
1340 &api,
1341 &mut staking_storage,
1342 &block,
1343 &delegator1,
1344 &validator,
1345 coin(100, "TOKEN"),
1346 )
1347 .unwrap();
1348
1349 block.time = block.time.plus_seconds(60 * 60 * 24 * 365);
1351
1352 let rewards = stake
1354 .get_rewards(&store, &block, &delegator1, &validator)
1355 .unwrap()
1356 .unwrap();
1357 assert_eq!(rewards.amount.u128(), 27);
1358
1359 let rewards = stake
1361 .get_rewards(&store, &block, &delegator2, &validator)
1362 .unwrap()
1363 .unwrap();
1364 assert_eq!(rewards.amount.u128(), 36);
1365
1366 let mut staking_storage = prefixed(&mut store, NAMESPACE_STAKING);
1368 stake
1369 .remove_stake(
1370 &api,
1371 &mut staking_storage,
1372 &block,
1373 &delegator2,
1374 &validator,
1375 coin(100, "TOKEN"),
1376 )
1377 .unwrap();
1378
1379 distr
1381 .execute(
1382 &api,
1383 &mut store,
1384 &router,
1385 &block,
1386 delegator1.clone(),
1387 DistributionMsg::WithdrawDelegatorReward {
1388 validator: validator.to_string(),
1389 },
1390 )
1391 .unwrap();
1392
1393 let balance: BalanceResponse = from_slice(
1394 &bank
1395 .query(
1396 &api,
1397 &store,
1398 &router.querier(&api, &store, &block),
1399 &block,
1400 BankQuery::Balance {
1401 address: delegator1.to_string(),
1402 denom: "TOKEN".to_string(),
1403 },
1404 )
1405 .unwrap(),
1406 )
1407 .unwrap();
1408 assert_eq!(
1409 balance.amount.amount.u128(),
1410 27,
1411 "withdraw should change bank balance"
1412 );
1413 let rewards = stake
1414 .get_rewards(&store, &block, &delegator1, &validator)
1415 .unwrap()
1416 .unwrap();
1417 assert_eq!(
1418 rewards.amount.u128(),
1419 0,
1420 "withdraw should reduce rewards to 0"
1421 );
1422
1423 block.time = block.time.plus_seconds(60 * 60 * 24 * 365);
1425
1426 let rewards = stake
1428 .get_rewards(&store, &block, &delegator1, &validator)
1429 .unwrap()
1430 .unwrap();
1431 assert_eq!(rewards.amount.u128(), 18);
1432
1433 let rewards = stake
1435 .get_rewards(&store, &block, &delegator2, &validator)
1436 .unwrap()
1437 .unwrap();
1438 assert_eq!(rewards.amount.u128(), 45);
1439 }
1440
1441 mod msg {
1442 use cosmwasm_std::{coins, from_slice, Addr, BondedDenomResponse, Decimal, StakingQuery};
1443 use serde::de::DeserializeOwned;
1444
1445 use super::*;
1446
1447 struct TestEnv {
1449 api: MockApi,
1450 store: MockStorage,
1451 router: BasicRouter,
1452 block: BlockInfo,
1453 }
1454
1455 impl TestEnv {
1456 fn wrap(tuple: (MockApi, MockStorage, BasicRouter, BlockInfo, Addr)) -> (Self, Addr) {
1457 (
1458 Self {
1459 api: tuple.0,
1460 store: tuple.1,
1461 router: tuple.2,
1462 block: tuple.3,
1463 },
1464 tuple.4,
1465 )
1466 }
1467 }
1468
1469 fn execute_stake(
1470 env: &mut TestEnv,
1471 sender: Addr,
1472 msg: StakingMsg,
1473 ) -> AnyResult<AppResponse> {
1474 env.router.staking.execute(
1475 &env.api,
1476 &mut env.store,
1477 &env.router,
1478 &env.block,
1479 sender,
1480 msg,
1481 )
1482 }
1483
1484 fn query_stake<T: DeserializeOwned>(env: &TestEnv, msg: StakingQuery) -> AnyResult<T> {
1485 Ok(from_slice(&env.router.staking.query(
1486 &env.api,
1487 &env.store,
1488 &env.router.querier(&env.api, &env.store, &env.block),
1489 &env.block,
1490 msg,
1491 )?)?)
1492 }
1493
1494 fn execute_distr(
1495 env: &mut TestEnv,
1496 sender: Addr,
1497 msg: DistributionMsg,
1498 ) -> AnyResult<AppResponse> {
1499 env.router.distribution.execute(
1500 &env.api,
1501 &mut env.store,
1502 &env.router,
1503 &env.block,
1504 sender,
1505 msg,
1506 )
1507 }
1508
1509 fn query_bank<T: DeserializeOwned>(env: &TestEnv, msg: BankQuery) -> AnyResult<T> {
1510 Ok(from_slice(&env.router.bank.query(
1511 &env.api,
1512 &env.store,
1513 &env.router.querier(&env.api, &env.store, &env.block),
1514 &env.block,
1515 msg,
1516 )?)?)
1517 }
1518
1519 fn assert_balances(env: &TestEnv, balances: impl IntoIterator<Item = (Addr, u128)>) {
1520 for (addr, amount) in balances {
1521 let balance: BalanceResponse = query_bank(
1522 env,
1523 BankQuery::Balance {
1524 address: addr.to_string(),
1525 denom: "TOKEN".to_string(),
1526 },
1527 )
1528 .unwrap();
1529 assert_eq!(balance.amount.amount.u128(), amount);
1530 }
1531 }
1532
1533 #[test]
1534 fn execute() {
1535 let (mut test_env, validator1) =
1537 TestEnv::wrap(setup_test_env(Decimal::percent(10), Decimal::percent(10)));
1538
1539 let delegator1 = Addr::unchecked("delegator1");
1540 let reward_receiver = Addr::unchecked("rewardreceiver");
1541
1542 test_env
1544 .router
1545 .bank
1546 .init_balance(&mut test_env.store, &delegator1, vec![coin(1000, "TOKEN")])
1547 .unwrap();
1548
1549 let validator2 = Addr::unchecked("validator2");
1551 test_env
1552 .router
1553 .staking
1554 .add_validator(
1555 &test_env.api,
1556 &mut test_env.store,
1557 &test_env.block,
1558 Validator {
1559 address: validator2.to_string(),
1560 commission: Decimal::zero(),
1561 max_commission: Decimal::percent(20),
1562 max_change_rate: Decimal::percent(1),
1563 },
1564 )
1565 .unwrap();
1566
1567 execute_stake(
1569 &mut test_env,
1570 delegator1.clone(),
1571 StakingMsg::Delegate {
1572 validator: validator1.to_string(),
1573 amount: coin(100, "TOKEN"),
1574 },
1575 )
1576 .unwrap();
1577
1578 assert_balances(&test_env, vec![(delegator1.clone(), 900)]);
1580
1581 test_env.block.time = test_env.block.time.plus_seconds(60 * 60 * 24 * 365);
1583
1584 execute_distr(
1586 &mut test_env,
1587 delegator1.clone(),
1588 DistributionMsg::SetWithdrawAddress {
1589 address: reward_receiver.to_string(),
1590 },
1591 )
1592 .unwrap();
1593
1594 execute_distr(
1596 &mut test_env,
1597 delegator1.clone(),
1598 DistributionMsg::WithdrawDelegatorReward {
1599 validator: validator1.to_string(),
1600 },
1601 )
1602 .unwrap();
1603
1604 assert_balances(
1606 &test_env,
1607 vec![(reward_receiver, 100 / 10 * 9 / 10)],
1609 );
1610
1611 execute_stake(
1613 &mut test_env,
1614 delegator1.clone(),
1615 StakingMsg::Redelegate {
1616 src_validator: validator1.to_string(),
1617 dst_validator: validator2.to_string(),
1618 amount: coin(100, "TOKEN"),
1619 },
1620 )
1621 .unwrap();
1622
1623 assert_balances(&test_env, vec![(delegator1.clone(), 900)]);
1625
1626 let delegations: AllDelegationsResponse = query_stake(
1627 &test_env,
1628 StakingQuery::AllDelegations {
1629 delegator: delegator1.to_string(),
1630 },
1631 )
1632 .unwrap();
1633 assert_eq!(
1634 delegations.delegations,
1635 [Delegation {
1636 delegator: delegator1.clone(),
1637 validator: validator2.to_string(),
1638 amount: coin(100, "TOKEN"),
1639 }]
1640 );
1641
1642 execute_stake(
1644 &mut test_env,
1645 delegator1.clone(),
1646 StakingMsg::Undelegate {
1647 validator: validator2.to_string(),
1648 amount: coin(100, "TOKEN"),
1649 },
1650 )
1651 .unwrap();
1652
1653 test_env.block.time = test_env.block.time.plus_seconds(60);
1655
1656 test_env
1658 .router
1659 .staking
1660 .sudo(
1661 &test_env.api,
1662 &mut test_env.store,
1663 &test_env.router,
1664 &test_env.block,
1665 StakingSudo::ProcessQueue {},
1666 )
1667 .unwrap();
1668
1669 assert_balances(&test_env, vec![(delegator1.clone(), 1000)]);
1671 }
1672
1673 #[test]
1674 fn can_set_withdraw_address() {
1675 let (mut test_env, validator) =
1676 TestEnv::wrap(setup_test_env(Decimal::percent(10), Decimal::percent(10)));
1677
1678 let delegator = Addr::unchecked("delegator");
1679 let reward_receiver = Addr::unchecked("rewardreceiver");
1680
1681 test_env
1682 .router
1683 .bank
1684 .init_balance(&mut test_env.store, &delegator, coins(100, "TOKEN"))
1685 .unwrap();
1686
1687 execute_stake(
1689 &mut test_env,
1690 delegator.clone(),
1691 StakingMsg::Delegate {
1692 validator: validator.to_string(),
1693 amount: coin(100, "TOKEN"),
1694 },
1695 )
1696 .unwrap();
1697
1698 execute_distr(
1700 &mut test_env,
1701 delegator.clone(),
1702 DistributionMsg::SetWithdrawAddress {
1703 address: reward_receiver.to_string(),
1704 },
1705 )
1706 .unwrap();
1707
1708 test_env.block.time = test_env.block.time.plus_seconds(60 * 60 * 24 * 365);
1710
1711 execute_distr(
1713 &mut test_env,
1714 delegator.clone(),
1715 DistributionMsg::WithdrawDelegatorReward {
1716 validator: validator.to_string(),
1717 },
1718 )
1719 .unwrap();
1720
1721 execute_distr(
1723 &mut test_env,
1724 delegator.clone(),
1725 DistributionMsg::SetWithdrawAddress {
1726 address: delegator.to_string(),
1727 },
1728 )
1729 .unwrap();
1730
1731 test_env.block.time = test_env.block.time.plus_seconds(60 * 60 * 24 * 365);
1733
1734 execute_distr(
1736 &mut test_env,
1737 delegator.clone(),
1738 DistributionMsg::WithdrawDelegatorReward {
1739 validator: validator.to_string(),
1740 },
1741 )
1742 .unwrap();
1743
1744 let rewards_yr = 100 / 10 * 9 / 10;
1746
1747 assert_balances(
1748 &test_env,
1749 vec![(reward_receiver, rewards_yr), (delegator, rewards_yr)],
1750 );
1751 }
1752
1753 #[test]
1754 fn cannot_steal() {
1755 let (mut test_env, validator1) =
1756 TestEnv::wrap(setup_test_env(Decimal::percent(10), Decimal::percent(10)));
1757
1758 let delegator1 = Addr::unchecked("delegator1");
1759
1760 test_env
1762 .router
1763 .bank
1764 .init_balance(&mut test_env.store, &delegator1, vec![coin(100, "TOKEN")])
1765 .unwrap();
1766
1767 execute_stake(
1769 &mut test_env,
1770 delegator1.clone(),
1771 StakingMsg::Delegate {
1772 validator: validator1.to_string(),
1773 amount: coin(100, "TOKEN"),
1774 },
1775 )
1776 .unwrap();
1777
1778 let e = execute_stake(
1780 &mut test_env,
1781 delegator1.clone(),
1782 StakingMsg::Undelegate {
1783 validator: validator1.to_string(),
1784 amount: coin(200, "TOKEN"),
1785 },
1786 )
1787 .unwrap_err();
1788
1789 assert_eq!(e.to_string(), "insufficient stake");
1790
1791 let validator2 = Addr::unchecked("validator2");
1793 test_env
1794 .router
1795 .staking
1796 .add_validator(
1797 &test_env.api,
1798 &mut test_env.store,
1799 &test_env.block,
1800 Validator {
1801 address: validator2.to_string(),
1802 commission: Decimal::zero(),
1803 max_commission: Decimal::percent(20),
1804 max_change_rate: Decimal::percent(1),
1805 },
1806 )
1807 .unwrap();
1808
1809 let e = execute_stake(
1811 &mut test_env,
1812 delegator1.clone(),
1813 StakingMsg::Redelegate {
1814 src_validator: validator1.to_string(),
1815 dst_validator: validator2.to_string(),
1816 amount: coin(200, "TOKEN"),
1817 },
1818 )
1819 .unwrap_err();
1820 assert_eq!(e.to_string(), "insufficient stake");
1821 }
1822
1823 #[test]
1824 fn denom_validation() {
1825 let (mut test_env, validator) =
1826 TestEnv::wrap(setup_test_env(Decimal::percent(10), Decimal::percent(10)));
1827
1828 let delegator1 = Addr::unchecked("delegator1");
1829
1830 test_env
1832 .router
1833 .bank
1834 .init_balance(&mut test_env.store, &delegator1, vec![coin(100, "FAKE")])
1835 .unwrap();
1836
1837 let e = execute_stake(
1839 &mut test_env,
1840 delegator1.clone(),
1841 StakingMsg::Delegate {
1842 validator: validator.to_string(),
1843 amount: coin(100, "FAKE"),
1844 },
1845 )
1846 .unwrap_err();
1847
1848 assert_eq!(
1849 e.to_string(),
1850 "cannot delegate coins of denominator FAKE, only of TOKEN",
1851 );
1852 }
1853
1854 #[test]
1855 fn cannot_slash_nonexistent() {
1856 let (mut test_env, _) =
1857 TestEnv::wrap(setup_test_env(Decimal::percent(10), Decimal::percent(10)));
1858
1859 let delegator1 = Addr::unchecked("delegator1");
1860
1861 test_env
1863 .router
1864 .bank
1865 .init_balance(&mut test_env.store, &delegator1, vec![coin(100, "FAKE")])
1866 .unwrap();
1867
1868 let e = test_env
1870 .router
1871 .staking
1872 .sudo(
1873 &test_env.api,
1874 &mut test_env.store,
1875 &test_env.router,
1876 &test_env.block,
1877 StakingSudo::Slash {
1878 validator: "nonexistingvaloper".to_string(),
1879 percentage: Decimal::percent(50),
1880 },
1881 )
1882 .unwrap_err();
1883 assert_eq!(e.to_string(), "validator nonexistingvaloper not found");
1884 }
1885
1886 #[test]
1887 fn zero_staking_allowed() {
1888 let (mut test_env, validator) =
1889 TestEnv::wrap(setup_test_env(Decimal::percent(10), Decimal::percent(10)));
1890
1891 let delegator = Addr::unchecked("delegator1");
1892
1893 execute_stake(
1895 &mut test_env,
1896 delegator.clone(),
1897 StakingMsg::Delegate {
1898 validator: validator.to_string(),
1899 amount: coin(0, "TOKEN"),
1900 },
1901 )
1902 .unwrap();
1903
1904 execute_stake(
1906 &mut test_env,
1907 delegator,
1908 StakingMsg::Undelegate {
1909 validator: validator.to_string(),
1910 amount: coin(0, "TOKEN"),
1911 },
1912 )
1913 .unwrap();
1914 }
1915
1916 #[test]
1917 fn query_staking() {
1918 let (mut test_env, validator1) =
1920 TestEnv::wrap(setup_test_env(Decimal::percent(10), Decimal::percent(10)));
1921 let delegator1 = Addr::unchecked("delegator1");
1922 let delegator2 = Addr::unchecked("delegator2");
1923
1924 test_env
1926 .router
1927 .bank
1928 .init_balance(&mut test_env.store, &delegator1, vec![coin(260, "TOKEN")])
1929 .unwrap();
1930 test_env
1931 .router
1932 .bank
1933 .init_balance(&mut test_env.store, &delegator2, vec![coin(150, "TOKEN")])
1934 .unwrap();
1935
1936 let validator2 = test_env.api.addr_validate("testvaloper2").unwrap();
1938 let valoper2 = Validator {
1939 address: "testvaloper2".to_string(),
1940 commission: Decimal::percent(0),
1941 max_commission: Decimal::percent(1),
1942 max_change_rate: Decimal::percent(1),
1943 };
1944 test_env
1945 .router
1946 .staking
1947 .add_validator(
1948 &test_env.api,
1949 &mut test_env.store,
1950 &test_env.block,
1951 valoper2.clone(),
1952 )
1953 .unwrap();
1954
1955 let valoper1: ValidatorResponse = query_stake(
1957 &test_env,
1958 StakingQuery::Validator {
1959 address: validator1.to_string(),
1960 },
1961 )
1962 .unwrap();
1963 let validators: AllValidatorsResponse =
1964 query_stake(&test_env, StakingQuery::AllValidators {}).unwrap();
1965 assert_eq!(
1966 validators.validators,
1967 [valoper1.validator.unwrap(), valoper2]
1968 );
1969 let response = query_stake::<ValidatorResponse>(
1971 &test_env,
1972 StakingQuery::Validator {
1973 address: "notvaloper".to_string(),
1974 },
1975 )
1976 .unwrap();
1977 assert_eq!(response.validator, None);
1978
1979 let response: BondedDenomResponse =
1981 query_stake(&test_env, StakingQuery::BondedDenom {}).unwrap();
1982 assert_eq!(response.denom, "TOKEN");
1983
1984 execute_stake(
1986 &mut test_env,
1987 delegator1.clone(),
1988 StakingMsg::Delegate {
1989 validator: validator1.to_string(),
1990 amount: coin(100, "TOKEN"),
1991 },
1992 )
1993 .unwrap();
1994 execute_stake(
1995 &mut test_env,
1996 delegator1.clone(),
1997 StakingMsg::Delegate {
1998 validator: validator2.to_string(),
1999 amount: coin(160, "TOKEN"),
2000 },
2001 )
2002 .unwrap();
2003 execute_stake(
2004 &mut test_env,
2005 delegator2.clone(),
2006 StakingMsg::Delegate {
2007 validator: validator1.to_string(),
2008 amount: coin(150, "TOKEN"),
2009 },
2010 )
2011 .unwrap();
2012 execute_stake(
2014 &mut test_env,
2015 delegator1.clone(),
2016 StakingMsg::Undelegate {
2017 validator: validator1.to_string(),
2018 amount: coin(50, "TOKEN"),
2019 },
2020 )
2021 .unwrap();
2022 execute_stake(
2023 &mut test_env,
2024 delegator2.clone(),
2025 StakingMsg::Undelegate {
2026 validator: validator1.to_string(),
2027 amount: coin(50, "TOKEN"),
2028 },
2029 )
2030 .unwrap();
2031
2032 let response1: AllDelegationsResponse = query_stake(
2034 &test_env,
2035 StakingQuery::AllDelegations {
2036 delegator: delegator1.to_string(),
2037 },
2038 )
2039 .unwrap();
2040 assert_eq!(
2041 response1.delegations,
2042 vec![
2043 Delegation {
2044 delegator: delegator1.clone(),
2045 validator: validator1.to_string(),
2046 amount: coin(100, "TOKEN"),
2047 },
2048 Delegation {
2049 delegator: delegator1.clone(),
2050 validator: validator2.to_string(),
2051 amount: coin(160, "TOKEN"),
2052 },
2053 ]
2054 );
2055 let response2: DelegationResponse = query_stake(
2056 &test_env,
2057 StakingQuery::Delegation {
2058 delegator: delegator2.to_string(),
2059 validator: validator1.to_string(),
2060 },
2061 )
2062 .unwrap();
2063 assert_eq!(
2064 response2.delegation.unwrap(),
2065 FullDelegation {
2066 delegator: delegator2.clone(),
2067 validator: validator1.to_string(),
2068 amount: coin(150, "TOKEN"),
2069 accumulated_rewards: vec![],
2070 can_redelegate: coin(150, "TOKEN"),
2071 },
2072 );
2073 }
2074
2075 #[test]
2076 fn delegation_queries_unbonding() {
2077 let (mut test_env, validator) =
2079 TestEnv::wrap(setup_test_env(Decimal::percent(10), Decimal::percent(10)));
2080 let delegator1 = Addr::unchecked("delegator1");
2081 let delegator2 = Addr::unchecked("delegator2");
2082
2083 test_env
2085 .router
2086 .bank
2087 .init_balance(&mut test_env.store, &delegator1, vec![coin(100, "TOKEN")])
2088 .unwrap();
2089 test_env
2090 .router
2091 .bank
2092 .init_balance(&mut test_env.store, &delegator2, vec![coin(150, "TOKEN")])
2093 .unwrap();
2094
2095 execute_stake(
2097 &mut test_env,
2098 delegator1.clone(),
2099 StakingMsg::Delegate {
2100 validator: validator.to_string(),
2101 amount: coin(100, "TOKEN"),
2102 },
2103 )
2104 .unwrap();
2105 execute_stake(
2106 &mut test_env,
2107 delegator2.clone(),
2108 StakingMsg::Delegate {
2109 validator: validator.to_string(),
2110 amount: coin(150, "TOKEN"),
2111 },
2112 )
2113 .unwrap();
2114 execute_stake(
2116 &mut test_env,
2117 delegator1.clone(),
2118 StakingMsg::Undelegate {
2119 validator: validator.to_string(),
2120 amount: coin(50, "TOKEN"),
2121 },
2122 )
2123 .unwrap();
2124 execute_stake(
2126 &mut test_env,
2127 delegator2.clone(),
2128 StakingMsg::Undelegate {
2129 validator: validator.to_string(),
2130 amount: coin(150, "TOKEN"),
2131 },
2132 )
2133 .unwrap();
2134
2135 let response1: AllDelegationsResponse = query_stake(
2137 &test_env,
2138 StakingQuery::AllDelegations {
2139 delegator: delegator1.to_string(),
2140 },
2141 )
2142 .unwrap();
2143 assert_eq!(
2144 response1.delegations,
2145 vec![Delegation {
2146 delegator: delegator1.clone(),
2147 validator: validator.to_string(),
2148 amount: coin(100, "TOKEN"),
2149 }]
2150 );
2151 let response2: DelegationResponse = query_stake(
2152 &test_env,
2153 StakingQuery::Delegation {
2154 delegator: delegator2.to_string(),
2155 validator: validator.to_string(),
2156 },
2157 )
2158 .unwrap();
2159 assert_eq!(
2160 response2.delegation.unwrap(),
2161 FullDelegation {
2162 delegator: delegator2.clone(),
2163 validator: validator.to_string(),
2164 amount: coin(150, "TOKEN"),
2165 accumulated_rewards: vec![],
2166 can_redelegate: coin(150, "TOKEN"),
2167 },
2168 );
2169
2170 test_env.block.time = test_env.block.time.plus_seconds(60);
2172 test_env
2173 .router
2174 .staking
2175 .sudo(
2176 &test_env.api,
2177 &mut test_env.store,
2178 &test_env.router,
2179 &test_env.block,
2180 StakingSudo::ProcessQueue {},
2181 )
2182 .unwrap();
2183
2184 let response1: AllDelegationsResponse = query_stake(
2186 &test_env,
2187 StakingQuery::AllDelegations {
2188 delegator: delegator1.to_string(),
2189 },
2190 )
2191 .unwrap();
2192 assert_eq!(
2193 response1.delegations,
2194 vec![Delegation {
2195 delegator: delegator1.clone(),
2196 validator: validator.to_string(),
2197 amount: coin(50, "TOKEN"),
2198 }],
2199 "delegator1 should have less now"
2200 );
2201 let response2: DelegationResponse = query_stake(
2202 &test_env,
2203 StakingQuery::Delegation {
2204 delegator: delegator2.to_string(),
2205 validator: validator.to_string(),
2206 },
2207 )
2208 .unwrap();
2209 assert_eq!(
2210 response2.delegation, None,
2211 "delegator2 should have nothing left"
2212 );
2213
2214 execute_stake(
2216 &mut test_env,
2217 delegator1.clone(),
2218 StakingMsg::Undelegate {
2219 validator: validator.to_string(),
2220 amount: coin(25, "TOKEN"),
2221 },
2222 )
2223 .unwrap();
2224 test_env.block.time = test_env.block.time.plus_seconds(10);
2225 execute_stake(
2226 &mut test_env,
2227 delegator1.clone(),
2228 StakingMsg::Undelegate {
2229 validator: validator.to_string(),
2230 amount: coin(25, "TOKEN"),
2231 },
2232 )
2233 .unwrap();
2234
2235 let response1: DelegationResponse = query_stake(
2237 &test_env,
2238 StakingQuery::Delegation {
2239 delegator: delegator1.to_string(),
2240 validator: validator.to_string(),
2241 },
2242 )
2243 .unwrap();
2244 let response2: AllDelegationsResponse = query_stake(
2245 &test_env,
2246 StakingQuery::AllDelegations {
2247 delegator: delegator1.to_string(),
2248 },
2249 )
2250 .unwrap();
2251 assert_eq!(
2252 response1.delegation.unwrap().amount.amount.u128(),
2253 50,
2254 "delegator1 should still have 50 tokens unbonding"
2255 );
2256 assert_eq!(response2.delegations[0].amount.amount.u128(), 50);
2257
2258 test_env.block.time = test_env.block.time.plus_seconds(60);
2260 test_env
2261 .router
2262 .staking
2263 .sudo(
2264 &test_env.api,
2265 &mut test_env.store,
2266 &test_env.router,
2267 &test_env.block,
2268 StakingSudo::ProcessQueue {},
2269 )
2270 .unwrap();
2271
2272 let response1: DelegationResponse = query_stake(
2274 &test_env,
2275 StakingQuery::Delegation {
2276 delegator: delegator1.to_string(),
2277 validator: validator.to_string(),
2278 },
2279 )
2280 .unwrap();
2281 let response2: AllDelegationsResponse = query_stake(
2282 &test_env,
2283 StakingQuery::AllDelegations {
2284 delegator: delegator1.to_string(),
2285 },
2286 )
2287 .unwrap();
2288 assert_eq!(
2289 response1.delegation, None,
2290 "delegator1 should have nothing left"
2291 );
2292 assert!(response2.delegations.is_empty());
2293 }
2294
2295 #[test]
2296 fn partial_unbonding_keeps_stake() {
2297 let (mut test_env, validator) =
2298 TestEnv::wrap(setup_test_env(Decimal::percent(10), Decimal::percent(10)));
2299 let delegator = Addr::unchecked("delegator1");
2300
2301 test_env
2303 .router
2304 .bank
2305 .init_balance(&mut test_env.store, &delegator, vec![coin(100, "TOKEN")])
2306 .unwrap();
2307
2308 execute_stake(
2310 &mut test_env,
2311 delegator.clone(),
2312 StakingMsg::Delegate {
2313 validator: validator.to_string(),
2314 amount: coin(100, "TOKEN"),
2315 },
2316 )
2317 .unwrap();
2318 execute_stake(
2320 &mut test_env,
2321 delegator.clone(),
2322 StakingMsg::Undelegate {
2323 validator: validator.to_string(),
2324 amount: coin(50, "TOKEN"),
2325 },
2326 )
2327 .unwrap();
2328 test_env.block.time = test_env.block.time.plus_seconds(10);
2329 execute_stake(
2330 &mut test_env,
2331 delegator.clone(),
2332 StakingMsg::Undelegate {
2333 validator: validator.to_string(),
2334 amount: coin(30, "TOKEN"),
2335 },
2336 )
2337 .unwrap();
2338 test_env.block.time = test_env.block.time.plus_seconds(10);
2339 execute_stake(
2340 &mut test_env,
2341 delegator.clone(),
2342 StakingMsg::Undelegate {
2343 validator: validator.to_string(),
2344 amount: coin(20, "TOKEN"),
2345 },
2346 )
2347 .unwrap();
2348
2349 test_env.block.time = test_env.block.time.plus_seconds(40);
2351 test_env
2352 .router
2353 .staking
2354 .sudo(
2355 &test_env.api,
2356 &mut test_env.store,
2357 &test_env.router,
2358 &test_env.block,
2359 StakingSudo::ProcessQueue {},
2360 )
2361 .unwrap();
2362
2363 let response1: DelegationResponse = query_stake(
2366 &test_env,
2367 StakingQuery::Delegation {
2368 delegator: delegator.to_string(),
2369 validator: validator.to_string(),
2370 },
2371 )
2372 .unwrap();
2373 let response2: AllDelegationsResponse = query_stake(
2374 &test_env,
2375 StakingQuery::AllDelegations {
2376 delegator: delegator.to_string(),
2377 },
2378 )
2379 .unwrap();
2380 assert_eq!(
2381 response1.delegation,
2382 Some(FullDelegation {
2383 delegator: delegator.clone(),
2384 validator: validator.to_string(),
2385 amount: coin(50, "TOKEN"),
2386 can_redelegate: coin(50, "TOKEN"),
2387 accumulated_rewards: vec![],
2388 })
2389 );
2390 assert_eq!(
2391 response2.delegations,
2392 vec![Delegation {
2393 delegator: delegator.clone(),
2394 validator: validator.to_string(),
2395 amount: coin(50, "TOKEN"),
2396 }]
2397 );
2398
2399 test_env.block.time = test_env.block.time.plus_seconds(20);
2401 test_env
2402 .router
2403 .staking
2404 .sudo(
2405 &test_env.api,
2406 &mut test_env.store,
2407 &test_env.router,
2408 &test_env.block,
2409 StakingSudo::ProcessQueue {},
2410 )
2411 .unwrap();
2412
2413 let response1: DelegationResponse = query_stake(
2415 &test_env,
2416 StakingQuery::Delegation {
2417 delegator: delegator.to_string(),
2418 validator: validator.to_string(),
2419 },
2420 )
2421 .unwrap();
2422 let response2: AllDelegationsResponse = query_stake(
2423 &test_env,
2424 StakingQuery::AllDelegations {
2425 delegator: delegator.to_string(),
2426 },
2427 )
2428 .unwrap();
2429 assert_eq!(
2430 response1.delegation, None,
2431 "delegator should have nothing left"
2432 );
2433 assert!(response2.delegations.is_empty());
2434 }
2435
2436 #[test]
2437 fn delegation_queries_slashed() {
2438 let (mut test_env, validator) =
2440 TestEnv::wrap(setup_test_env(Decimal::percent(10), Decimal::percent(10)));
2441 let delegator = Addr::unchecked("delegator");
2442
2443 test_env
2445 .router
2446 .bank
2447 .init_balance(&mut test_env.store, &delegator, vec![coin(333, "TOKEN")])
2448 .unwrap();
2449
2450 execute_stake(
2452 &mut test_env,
2453 delegator.clone(),
2454 StakingMsg::Delegate {
2455 validator: validator.to_string(),
2456 amount: coin(333, "TOKEN"),
2457 },
2458 )
2459 .unwrap();
2460 execute_stake(
2462 &mut test_env,
2463 delegator.clone(),
2464 StakingMsg::Undelegate {
2465 validator: validator.to_string(),
2466 amount: coin(111, "TOKEN"),
2467 },
2468 )
2469 .unwrap();
2470
2471 test_env
2473 .router
2474 .staking
2475 .sudo(
2476 &test_env.api,
2477 &mut test_env.store,
2478 &test_env.router,
2479 &test_env.block,
2480 StakingSudo::Slash {
2481 validator: "testvaloper1".to_string(),
2482 percentage: Decimal::percent(50),
2483 },
2484 )
2485 .unwrap();
2486
2487 let response1: AllDelegationsResponse = query_stake(
2489 &test_env,
2490 StakingQuery::AllDelegations {
2491 delegator: delegator.to_string(),
2492 },
2493 )
2494 .unwrap();
2495 assert_eq!(
2496 response1.delegations[0],
2497 Delegation {
2498 delegator: delegator.clone(),
2499 validator: validator.to_string(),
2500 amount: coin(166, "TOKEN"),
2501 }
2502 );
2503 }
2504 }
2505}