clone_cw_multi_test/
staking.rs

1use crate::app::CosmosRouter;
2use crate::error::{anyhow, bail, AnyResult};
3use crate::executor::AppResponse;
4use crate::prefixed_storage::{prefixed, prefixed_read};
5use crate::{BankSudo, Module};
6use cosmwasm_std::{
7    coin, ensure, ensure_eq, to_json_binary, Addr, AllDelegationsResponse, AllValidatorsResponse,
8    Api, 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 schemars::JsonSchema;
14use serde::{Deserialize, Serialize};
15use std::collections::{BTreeSet, VecDeque};
16
17// Contains some general staking parameters
18#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
19pub struct StakingInfo {
20    /// The denominator of the staking token
21    pub bonded_denom: String,
22    /// Time between unbonding and receiving tokens in seconds
23    pub unbonding_time: u64,
24    /// Interest rate per year (60 * 60 * 24 * 365 seconds)
25    pub apr: Decimal,
26}
27
28impl Default for StakingInfo {
29    fn default() -> Self {
30        StakingInfo {
31            bonded_denom: "TOKEN".to_string(),
32            unbonding_time: 60,
33            apr: Decimal::percent(10),
34        }
35    }
36}
37
38/// The number of stake and rewards of this validator the staker has. These can be fractional in case of slashing.
39#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, JsonSchema)]
40struct Shares {
41    stake: Decimal,
42    rewards: Decimal,
43}
44
45impl Shares {
46    /// Calculates the share of validator rewards that should be given to this staker.
47    pub fn share_of_rewards(&self, validator: &ValidatorInfo, rewards: Decimal) -> Decimal {
48        if validator.stake.is_zero() {
49            return Decimal::zero();
50        }
51        rewards * self.stake / validator.stake
52    }
53}
54
55/// Holds some operational data about a validator
56#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
57struct ValidatorInfo {
58    /// The stakers that have staked with this validator.
59    /// We need to track them for updating their rewards.
60    stakers: BTreeSet<Addr>,
61    /// The whole stake of all stakers
62    stake: Uint128,
63    /// The block time when this validator's rewards were last update. This is needed for rewards calculation.
64    last_rewards_calculation: Timestamp,
65}
66
67impl ValidatorInfo {
68    pub fn new(block_time: Timestamp) -> Self {
69        Self {
70            stakers: BTreeSet::new(),
71            stake: Uint128::zero(),
72            last_rewards_calculation: block_time,
73        }
74    }
75}
76
77#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
78struct Unbonding {
79    pub delegator: Addr,
80    pub validator: Addr,
81    pub amount: Uint128,
82    pub payout_at: Timestamp,
83}
84
85const STAKING_INFO: Item<StakingInfo> = Item::new("staking_info");
86/// (staker_addr, validator_addr) -> shares
87const STAKES: Map<(&Addr, &Addr), Shares> = Map::new("stakes");
88const VALIDATOR_MAP: Map<&Addr, Validator> = Map::new("validator_map");
89/// Additional vec of validators, in case the `iterator` feature is disabled
90const VALIDATORS: Deque<Validator> = Deque::new("validators");
91/// Contains additional info for each validator
92const VALIDATOR_INFO: Map<&Addr, ValidatorInfo> = Map::new("validator_info");
93/// The queue of unbonding operations. This is needed because unbonding has a waiting time. See [`StakeKeeper`]
94const UNBONDING_QUEUE: Item<VecDeque<Unbonding>> = Item::new("unbonding_queue");
95/// (addr) -> addr. Maps addresses to the address they have delegated
96/// to receive their staking rewards. A missing key => no delegation
97/// has been set.
98const WITHDRAW_ADDRESS: Map<&Addr, Addr> = Map::new("withdraw_address");
99
100pub const NAMESPACE_STAKING: &[u8] = b"staking";
101// https://github.com/cosmos/cosmos-sdk/blob/4f6f6c00021f4b5ee486bbb71ae2071a8ceb47c9/x/distribution/types/keys.go#L16
102pub const NAMESPACE_DISTRIBUTION: &[u8] = b"distribution";
103
104// We need to expand on this, but we will need this to properly test out staking
105#[derive(Clone, std::fmt::Debug, PartialEq, Eq, JsonSchema)]
106pub enum StakingSudo {
107    /// Slashes the given percentage of the validator's stake.
108    /// For now, you cannot slash retrospectively in tests.
109    Slash {
110        validator: String,
111        percentage: Decimal,
112    },
113    /// Causes the unbonding queue to be processed.
114    /// This needs to be triggered manually, since there is no good place to do this right now.
115    /// In cosmos-sdk, this is done in `EndBlock`, but we don't have that here.
116    #[deprecated(note = "This is not needed anymore. Just call `update_block`")]
117    ProcessQueue {},
118}
119
120pub trait Staking: Module<ExecT = StakingMsg, QueryT = StakingQuery, SudoT = StakingSudo> {
121    /// This is called from the end blocker (`update_block` / `set_block`) to process the
122    /// staking queue.
123    /// Needed because unbonding has a waiting time.
124    /// If you're implementing a dummy staking module, this can be a no-op.
125    fn process_queue<ExecC, QueryC: CustomQuery>(
126        &self,
127        api: &dyn Api,
128        storage: &mut dyn Storage,
129        router: &dyn CosmosRouter<ExecC = ExecC, QueryC = QueryC>,
130        block: &BlockInfo,
131    ) -> AnyResult<AppResponse>;
132}
133
134pub trait Distribution: Module<ExecT = DistributionMsg, QueryT = Empty, SudoT = Empty> {}
135
136pub struct StakeKeeper {
137    module_addr: Addr,
138}
139
140impl Default for StakeKeeper {
141    fn default() -> Self {
142        Self::new()
143    }
144}
145
146impl StakeKeeper {
147    pub fn new() -> Self {
148        StakeKeeper {
149            // The address of the staking module. This holds all staked tokens.
150            module_addr: Addr::unchecked("staking_module"),
151        }
152    }
153
154    /// Provides some general parameters to the stake keeper
155    pub fn setup(&self, storage: &mut dyn Storage, staking_info: StakingInfo) -> AnyResult<()> {
156        let mut storage = prefixed(storage, NAMESPACE_STAKING);
157
158        STAKING_INFO.save(&mut storage, &staking_info)?;
159        Ok(())
160    }
161
162    /// Add a new validator available for staking
163    pub fn add_validator(
164        &self,
165        api: &dyn Api,
166        storage: &mut dyn Storage,
167        block: &BlockInfo,
168        validator: Validator,
169    ) -> AnyResult<()> {
170        let mut storage = prefixed(storage, NAMESPACE_STAKING);
171
172        let val_addr = api.addr_validate(&validator.address)?;
173        if VALIDATOR_MAP.may_load(&storage, &val_addr)?.is_some() {
174            bail!(
175                "Cannot add validator {}, since a validator with that address already exists",
176                val_addr
177            );
178        }
179
180        VALIDATOR_MAP.save(&mut storage, &val_addr, &validator)?;
181        VALIDATORS.push_back(&mut storage, &validator)?;
182        VALIDATOR_INFO.save(&mut storage, &val_addr, &ValidatorInfo::new(block.time))?;
183        Ok(())
184    }
185
186    fn get_staking_info(staking_storage: &dyn Storage) -> AnyResult<StakingInfo> {
187        Ok(STAKING_INFO.may_load(staking_storage)?.unwrap_or_default())
188    }
189
190    /// Returns the rewards of the given delegator at the given validator
191    pub fn get_rewards(
192        &self,
193        storage: &dyn Storage,
194        block: &BlockInfo,
195        delegator: &Addr,
196        validator: &Addr,
197    ) -> AnyResult<Option<Coin>> {
198        let staking_storage = prefixed_read(storage, NAMESPACE_STAKING);
199
200        let validator_obj = match self.get_validator(&staking_storage, validator)? {
201            Some(validator) => validator,
202            None => bail!("validator {} not found", validator),
203        };
204        // calculate rewards using fixed ratio
205        let shares = match STAKES.load(&staking_storage, (delegator, validator)) {
206            Ok(stakes) => stakes,
207            Err(_) => {
208                return Ok(None);
209            }
210        };
211        let validator_info = VALIDATOR_INFO.load(&staking_storage, validator)?;
212
213        Self::get_rewards_internal(
214            &staking_storage,
215            block,
216            &shares,
217            &validator_obj,
218            &validator_info,
219        )
220        .map(Some)
221    }
222
223    fn get_rewards_internal(
224        staking_storage: &dyn Storage,
225        block: &BlockInfo,
226        shares: &Shares,
227        validator: &Validator,
228        validator_info: &ValidatorInfo,
229    ) -> AnyResult<Coin> {
230        let staking_info = Self::get_staking_info(staking_storage)?;
231
232        // calculate missing rewards without updating the validator to reduce rounding errors
233        let new_validator_rewards = Self::calculate_rewards(
234            block.time,
235            validator_info.last_rewards_calculation,
236            staking_info.apr,
237            validator.commission,
238            validator_info.stake,
239        );
240
241        // calculate the delegator's share of those
242        let delegator_rewards =
243            shares.rewards + shares.share_of_rewards(validator_info, new_validator_rewards);
244
245        Ok(Coin {
246            denom: staking_info.bonded_denom,
247            amount: Uint128::new(1).mul_floor(delegator_rewards), // multiplying by 1 to convert Decimal to Uint128
248        })
249    }
250
251    /// Calculates the rewards that are due since the last calculation.
252    fn calculate_rewards(
253        current_time: Timestamp,
254        since: Timestamp,
255        interest_rate: Decimal,
256        validator_commission: Decimal,
257        stake: Uint128,
258    ) -> Decimal {
259        // calculate time since last update (in seconds)
260        let time_diff = current_time.minus_seconds(since.seconds()).seconds();
261
262        // using decimal here to reduce rounding error when calling this function a lot
263        let reward = Decimal::from_ratio(stake, 1u128)
264            * interest_rate
265            * Decimal::from_ratio(time_diff, 1u128)
266            / Decimal::from_ratio(60u128 * 60 * 24 * 365, 1u128);
267        let commission = reward * validator_commission;
268
269        reward - commission
270    }
271
272    /// Updates the staking reward for the given validator and their stakers
273    /// It saves the validator info and it's stakers, so make sure not to overwrite that.
274    /// Always call this to update rewards before changing anything that influences future rewards.
275    fn update_rewards(
276        api: &dyn Api,
277        staking_storage: &mut dyn Storage,
278        block: &BlockInfo,
279        validator: &Addr,
280    ) -> AnyResult<()> {
281        let staking_info = Self::get_staking_info(staking_storage)?;
282
283        let mut validator_info = VALIDATOR_INFO
284            .may_load(staking_storage, validator)?
285            // https://github.com/cosmos/cosmos-sdk/blob/3c5387048f75d7e78b40c5b8d2421fdb8f5d973a/x/staking/types/errors.go#L15
286            .ok_or_else(|| anyhow!("validator does not exist"))?;
287
288        let validator_obj = VALIDATOR_MAP.load(staking_storage, validator)?;
289
290        if validator_info.last_rewards_calculation >= block.time {
291            return Ok(());
292        }
293
294        let new_rewards = Self::calculate_rewards(
295            block.time,
296            validator_info.last_rewards_calculation,
297            staking_info.apr,
298            validator_obj.commission,
299            validator_info.stake,
300        );
301
302        // update validator info
303        validator_info.last_rewards_calculation = block.time;
304        VALIDATOR_INFO.save(staking_storage, validator, &validator_info)?;
305
306        // update delegators
307        if !new_rewards.is_zero() {
308            let validator_addr = api.addr_validate(&validator_obj.address)?;
309            // update all delegators
310            for staker in validator_info.stakers.iter() {
311                STAKES.update(
312                    staking_storage,
313                    (staker, &validator_addr),
314                    |shares| -> AnyResult<_> {
315                        let mut shares =
316                            shares.expect("all stakers in validator_info should exist");
317                        shares.rewards += shares.share_of_rewards(&validator_info, new_rewards);
318                        Ok(shares)
319                    },
320                )?;
321            }
322        }
323        Ok(())
324    }
325
326    /// Returns the single validator with the given address (or `None` if there is no such validator)
327    fn get_validator(
328        &self,
329        staking_storage: &dyn Storage,
330        address: &Addr,
331    ) -> AnyResult<Option<Validator>> {
332        Ok(VALIDATOR_MAP.may_load(staking_storage, address)?)
333    }
334
335    /// Returns all available validators
336    fn get_validators(&self, staking_storage: &dyn Storage) -> AnyResult<Vec<Validator>> {
337        let res: Result<_, _> = VALIDATORS.iter(staking_storage)?.collect();
338        Ok(res?)
339    }
340
341    fn get_stake(
342        &self,
343        staking_storage: &dyn Storage,
344        account: &Addr,
345        validator: &Addr,
346    ) -> AnyResult<Option<Coin>> {
347        let shares = STAKES.may_load(staking_storage, (account, validator))?;
348        let staking_info = Self::get_staking_info(staking_storage)?;
349
350        Ok(shares.map(|shares| {
351            Coin {
352                denom: staking_info.bonded_denom,
353                amount: Uint128::new(1).mul_floor(shares.stake), // multiplying by 1 to convert Decimal to Uint128
354            }
355        }))
356    }
357
358    fn add_stake(
359        &self,
360        api: &dyn Api,
361        staking_storage: &mut dyn Storage,
362        block: &BlockInfo,
363        to_address: &Addr,
364        validator: &Addr,
365        amount: Coin,
366    ) -> AnyResult<()> {
367        self.validate_denom(staking_storage, &amount)?;
368        self.update_stake(
369            api,
370            staking_storage,
371            block,
372            to_address,
373            validator,
374            amount.amount,
375            false,
376        )
377    }
378
379    fn remove_stake(
380        &self,
381        api: &dyn Api,
382        staking_storage: &mut dyn Storage,
383        block: &BlockInfo,
384        from_address: &Addr,
385        validator: &Addr,
386        amount: Coin,
387    ) -> AnyResult<()> {
388        self.validate_denom(staking_storage, &amount)?;
389        self.update_stake(
390            api,
391            staking_storage,
392            block,
393            from_address,
394            validator,
395            amount.amount,
396            true,
397        )
398    }
399
400    fn update_stake(
401        &self,
402        api: &dyn Api,
403        staking_storage: &mut dyn Storage,
404        block: &BlockInfo,
405        delegator: &Addr,
406        validator: &Addr,
407        amount: impl Into<Uint128>,
408        sub: bool,
409    ) -> AnyResult<()> {
410        let amount = amount.into();
411
412        // update rewards for this validator
413        Self::update_rewards(api, staking_storage, block, validator)?;
414
415        // now, we can update the stake of the delegator and validator
416        let mut validator_info = VALIDATOR_INFO
417            .may_load(staking_storage, validator)?
418            .unwrap_or_else(|| ValidatorInfo::new(block.time));
419        let shares = STAKES.may_load(staking_storage, (delegator, validator))?;
420        let mut shares = if sub {
421            // see https://github.com/cosmos/cosmos-sdk/blob/3c5387048f75d7e78b40c5b8d2421fdb8f5d973a/x/staking/keeper/delegation.go#L1005-L1007
422            // and https://github.com/cosmos/cosmos-sdk/blob/3c5387048f75d7e78b40c5b8d2421fdb8f5d973a/x/staking/types/errors.go#L31
423            shares.ok_or_else(|| anyhow!("no delegation for (address, validator) tuple"))?
424        } else {
425            shares.unwrap_or_default()
426        };
427
428        let amount_dec = Decimal::from_ratio(amount, 1u128);
429        if sub {
430            // see https://github.com/cosmos/cosmos-sdk/blob/3c5387048f75d7e78b40c5b8d2421fdb8f5d973a/x/staking/keeper/delegation.go#L1019-L1022
431            if amount_dec > shares.stake {
432                bail!("invalid shares amount");
433            }
434            shares.stake -= amount_dec;
435            validator_info.stake = validator_info.stake.checked_sub(amount)?;
436        } else {
437            shares.stake += amount_dec;
438            validator_info.stake = validator_info.stake.checked_add(amount)?;
439        }
440
441        // save updated values
442        if shares.stake.is_zero() {
443            // no more stake, so remove
444            STAKES.remove(staking_storage, (delegator, validator));
445            validator_info.stakers.remove(delegator);
446        } else {
447            STAKES.save(staking_storage, (delegator, validator), &shares)?;
448            validator_info.stakers.insert(delegator.clone());
449        }
450        // save updated validator info
451        VALIDATOR_INFO.save(staking_storage, validator, &validator_info)?;
452
453        Ok(())
454    }
455
456    fn slash(
457        &self,
458        api: &dyn Api,
459        staking_storage: &mut dyn Storage,
460        block: &BlockInfo,
461        validator: &Addr,
462        percentage: Decimal,
463    ) -> AnyResult<()> {
464        // calculate rewards before slashing
465        Self::update_rewards(api, staking_storage, block, validator)?;
466
467        // update stake of validator and stakers
468        let mut validator_info = VALIDATOR_INFO
469            .may_load(staking_storage, validator)?
470            .unwrap();
471
472        let remaining_percentage = Decimal::one() - percentage;
473        validator_info.stake = validator_info.stake.mul_floor(remaining_percentage);
474
475        // if the stake is completely gone, we clear all stakers and reinitialize the validator
476        if validator_info.stake.is_zero() {
477            // need to remove all stakes
478            for delegator in validator_info.stakers.iter() {
479                STAKES.remove(staking_storage, (delegator, validator));
480            }
481            validator_info.stakers.clear();
482        } else {
483            // otherwise we update all stakers
484            for delegator in validator_info.stakers.iter() {
485                STAKES.update(
486                    staking_storage,
487                    (delegator, validator),
488                    |stake| -> AnyResult<_> {
489                        let mut stake = stake.expect("all stakers in validator_info should exist");
490                        stake.stake *= remaining_percentage;
491
492                        Ok(stake)
493                    },
494                )?;
495            }
496        }
497        // go through the queue to slash all pending unbondings
498        let mut unbonding_queue = UNBONDING_QUEUE
499            .may_load(staking_storage)?
500            .unwrap_or_default();
501        #[allow(clippy::op_ref)]
502        unbonding_queue
503            .iter_mut()
504            .filter(|ub| &ub.validator == validator)
505            .for_each(|ub| {
506                ub.amount = ub.amount.mul_floor(remaining_percentage);
507            });
508        UNBONDING_QUEUE.save(staking_storage, &unbonding_queue)?;
509
510        VALIDATOR_INFO.save(staking_storage, validator, &validator_info)?;
511        Ok(())
512    }
513
514    // Asserts that the given coin has the proper denominator
515    fn validate_denom(&self, staking_storage: &dyn Storage, amount: &Coin) -> AnyResult<()> {
516        let staking_info = Self::get_staking_info(staking_storage)?;
517        ensure_eq!(
518            amount.denom,
519            staking_info.bonded_denom,
520            anyhow!(
521                "cannot delegate coins of denominator {}, only of {}",
522                amount.denom,
523                staking_info.bonded_denom
524            )
525        );
526        Ok(())
527    }
528
529    // Asserts that the given coin has the proper denominator
530    fn validate_percentage(&self, percentage: Decimal) -> AnyResult<()> {
531        ensure!(percentage <= Decimal::one(), anyhow!("expected percentage"));
532        Ok(())
533    }
534
535    fn process_queue<ExecC, QueryC: CustomQuery>(
536        &self,
537        api: &dyn Api,
538        storage: &mut dyn Storage,
539        router: &dyn CosmosRouter<ExecC = ExecC, QueryC = QueryC>,
540        block: &BlockInfo,
541    ) -> AnyResult<AppResponse> {
542        let staking_storage = prefixed_read(storage, NAMESPACE_STAKING);
543        let mut unbonding_queue = UNBONDING_QUEUE
544            .may_load(&staking_storage)?
545            .unwrap_or_default();
546        loop {
547            let mut staking_storage = prefixed(storage, NAMESPACE_STAKING);
548            match unbonding_queue.front() {
549                // assuming the queue is sorted by payout_at
550                Some(Unbonding { payout_at, .. }) if payout_at <= &block.time => {
551                    // remove from queue
552                    let Unbonding {
553                        delegator,
554                        validator,
555                        amount,
556                        ..
557                    } = unbonding_queue.pop_front().unwrap();
558
559                    // remove staking entry if it is empty
560                    let delegation = self
561                        .get_stake(&staking_storage, &delegator, &validator)?
562                        .map(|mut stake| {
563                            // add unbonding amounts
564                            stake.amount += unbonding_queue
565                                .iter()
566                                .filter(|u| u.delegator == delegator && u.validator == validator)
567                                .map(|u| u.amount)
568                                .sum::<Uint128>();
569                            stake
570                        });
571                    match delegation {
572                        Some(delegation) if delegation.amount.is_zero() => {
573                            STAKES.remove(&mut staking_storage, (&delegator, &validator));
574                        }
575                        None => STAKES.remove(&mut staking_storage, (&delegator, &validator)),
576                        _ => {}
577                    }
578
579                    let staking_info = Self::get_staking_info(&staking_storage)?;
580                    if !amount.is_zero() {
581                        router.execute(
582                            api,
583                            storage,
584                            block,
585                            self.module_addr.clone(),
586                            BankMsg::Send {
587                                to_address: delegator.into_string(),
588                                amount: vec![coin(amount.u128(), &staking_info.bonded_denom)],
589                            }
590                            .into(),
591                        )?;
592                    }
593                }
594                _ => break,
595            }
596        }
597        let mut staking_storage = prefixed(storage, NAMESPACE_STAKING);
598        UNBONDING_QUEUE.save(&mut staking_storage, &unbonding_queue)?;
599        Ok(AppResponse::default())
600    }
601}
602
603impl Staking for StakeKeeper {
604    fn process_queue<ExecC, QueryC: CustomQuery>(
605        &self,
606        api: &dyn Api,
607        storage: &mut dyn Storage,
608        router: &dyn CosmosRouter<ExecC = ExecC, QueryC = QueryC>,
609        block: &BlockInfo,
610    ) -> AnyResult<AppResponse> {
611        self.process_queue(api, storage, router, block)
612    }
613}
614
615impl Module for StakeKeeper {
616    type ExecT = StakingMsg;
617    type QueryT = StakingQuery;
618    type SudoT = StakingSudo;
619
620    fn execute<ExecC, QueryC: CustomQuery>(
621        &self,
622        api: &dyn Api,
623        storage: &mut dyn Storage,
624        router: &dyn CosmosRouter<ExecC = ExecC, QueryC = QueryC>,
625        block: &BlockInfo,
626        sender: Addr,
627        msg: StakingMsg,
628    ) -> AnyResult<AppResponse> {
629        let mut staking_storage = prefixed(storage, NAMESPACE_STAKING);
630        match msg {
631            StakingMsg::Delegate { validator, amount } => {
632                let validator = api.addr_validate(&validator)?;
633
634                // see https://github.com/cosmos/cosmos-sdk/blob/3c5387048f75d7e78b40c5b8d2421fdb8f5d973a/x/staking/types/msg.go#L202-L207
635                if amount.amount.is_zero() {
636                    bail!("invalid delegation amount");
637                }
638
639                // see https://github.com/cosmos/cosmos-sdk/blob/v0.46.1/x/staking/keeper/msg_server.go#L251-L256
640                let events = vec![Event::new("delegate")
641                    .add_attribute("validator", &validator)
642                    .add_attribute("amount", format!("{}{}", amount.amount, amount.denom))
643                    .add_attribute("new_shares", amount.amount.to_string())]; // TODO: calculate shares?
644                self.add_stake(
645                    api,
646                    &mut staking_storage,
647                    block,
648                    &sender,
649                    &validator,
650                    amount.clone(),
651                )?;
652                // move money from sender account to this module (note we can control sender here)
653                router.execute(
654                    api,
655                    storage,
656                    block,
657                    sender,
658                    BankMsg::Send {
659                        to_address: self.module_addr.to_string(),
660                        amount: vec![amount],
661                    }
662                    .into(),
663                )?;
664                Ok(AppResponse { events, data: None })
665            }
666            StakingMsg::Undelegate { validator, amount } => {
667                let validator = api.addr_validate(&validator)?;
668                self.validate_denom(&staking_storage, &amount)?;
669
670                // see https://github.com/cosmos/cosmos-sdk/blob/3c5387048f75d7e78b40c5b8d2421fdb8f5d973a/x/staking/types/msg.go#L292-L297
671                if amount.amount.is_zero() {
672                    bail!("invalid shares amount");
673                }
674
675                // see https://github.com/cosmos/cosmos-sdk/blob/v0.46.1/x/staking/keeper/msg_server.go#L378-L383
676                let events = vec![Event::new("unbond")
677                    .add_attribute("validator", &validator)
678                    .add_attribute("amount", format!("{}{}", amount.amount, amount.denom))
679                    .add_attribute("completion_time", "2022-09-27T14:00:00+00:00")]; // TODO: actual date?
680                self.remove_stake(
681                    api,
682                    &mut staking_storage,
683                    block,
684                    &sender,
685                    &validator,
686                    amount.clone(),
687                )?;
688                // add tokens to unbonding queue
689                let staking_info = Self::get_staking_info(&staking_storage)?;
690                let mut unbonding_queue = UNBONDING_QUEUE
691                    .may_load(&staking_storage)?
692                    .unwrap_or_default();
693                unbonding_queue.push_back(Unbonding {
694                    delegator: sender.clone(),
695                    validator,
696                    amount: amount.amount,
697                    payout_at: block.time.plus_seconds(staking_info.unbonding_time),
698                });
699                UNBONDING_QUEUE.save(&mut staking_storage, &unbonding_queue)?;
700                Ok(AppResponse { events, data: None })
701            }
702            StakingMsg::Redelegate {
703                src_validator,
704                dst_validator,
705                amount,
706            } => {
707                let src_validator = api.addr_validate(&src_validator)?;
708                let dst_validator = api.addr_validate(&dst_validator)?;
709                // see https://github.com/cosmos/cosmos-sdk/blob/v0.46.1/x/staking/keeper/msg_server.go#L316-L322
710                let events = vec![Event::new("redelegate")
711                    .add_attribute("source_validator", &src_validator)
712                    .add_attribute("destination_validator", &dst_validator)
713                    .add_attribute("amount", format!("{}{}", amount.amount, amount.denom))];
714
715                self.remove_stake(
716                    api,
717                    &mut staking_storage,
718                    block,
719                    &sender,
720                    &src_validator,
721                    amount.clone(),
722                )?;
723                self.add_stake(
724                    api,
725                    &mut staking_storage,
726                    block,
727                    &sender,
728                    &dst_validator,
729                    amount,
730                )?;
731
732                Ok(AppResponse { events, data: None })
733            }
734            m => bail!("Unsupported staking message: {:?}", m),
735        }
736    }
737
738    fn sudo<ExecC, QueryC: CustomQuery>(
739        &self,
740        api: &dyn Api,
741        storage: &mut dyn Storage,
742        router: &dyn CosmosRouter<ExecC = ExecC, QueryC = QueryC>,
743        block: &BlockInfo,
744        msg: StakingSudo,
745    ) -> AnyResult<AppResponse> {
746        match msg {
747            StakingSudo::Slash {
748                validator,
749                percentage,
750            } => {
751                let mut staking_storage = prefixed(storage, NAMESPACE_STAKING);
752                let validator = api.addr_validate(&validator)?;
753                self.validate_percentage(percentage)?;
754
755                self.slash(api, &mut staking_storage, block, &validator, percentage)?;
756
757                Ok(AppResponse::default())
758            }
759            #[allow(deprecated)]
760            StakingSudo::ProcessQueue {} => self.process_queue(api, storage, router, block),
761        }
762    }
763
764    fn query(
765        &self,
766        api: &dyn Api,
767        storage: &dyn Storage,
768        _querier: &dyn Querier,
769        block: &BlockInfo,
770        request: StakingQuery,
771    ) -> AnyResult<Binary> {
772        let staking_storage = prefixed_read(storage, NAMESPACE_STAKING);
773        match request {
774            StakingQuery::BondedDenom {} => Ok(to_json_binary(&BondedDenomResponse::new(
775                Self::get_staking_info(&staking_storage)?.bonded_denom,
776            ))?),
777            StakingQuery::AllDelegations { delegator } => {
778                let delegator = api.addr_validate(&delegator)?;
779                let validators = self.get_validators(&staking_storage)?;
780
781                let res: AnyResult<Vec<Delegation>> =
782                    validators
783                        .into_iter()
784                        .filter_map(|validator| {
785                            let delegator = delegator.clone();
786                            let amount = self
787                                .get_stake(
788                                    &staking_storage,
789                                    &delegator,
790                                    &Addr::unchecked(&validator.address),
791                                )
792                                .transpose()?;
793
794                            Some(amount.map(|amount| {
795                                Delegation::new(delegator, validator.address, amount)
796                            }))
797                        })
798                        .collect();
799
800                Ok(to_json_binary(&AllDelegationsResponse::new(res?))?)
801            }
802            StakingQuery::Delegation {
803                delegator,
804                validator,
805            } => {
806                let validator_addr = Addr::unchecked(&validator);
807                let validator_obj = match self.get_validator(&staking_storage, &validator_addr)? {
808                    Some(validator) => validator,
809                    None => bail!("non-existent validator {}", validator),
810                };
811                let delegator = api.addr_validate(&delegator)?;
812
813                let shares = STAKES
814                    .may_load(&staking_storage, (&delegator, &validator_addr))?
815                    .unwrap_or_default();
816
817                let validator_info = VALIDATOR_INFO.load(&staking_storage, &validator_addr)?;
818                let reward = Self::get_rewards_internal(
819                    &staking_storage,
820                    block,
821                    &shares,
822                    &validator_obj,
823                    &validator_info,
824                )?;
825                let staking_info = Self::get_staking_info(&staking_storage)?;
826
827                let amount = coin(
828                    (Uint128::new(1).mul_floor(shares.stake)).u128(),
829                    staking_info.bonded_denom,
830                );
831
832                let full_delegation_response = if amount.amount.is_zero() {
833                    // no delegation
834                    DelegationResponse::new(None)
835                } else {
836                    DelegationResponse::new(Some(FullDelegation::new(
837                        delegator,
838                        validator,
839                        amount.clone(),
840                        amount, // TODO: not implemented right now
841                        if reward.amount.is_zero() {
842                            vec![]
843                        } else {
844                            vec![reward]
845                        },
846                    )))
847                };
848
849                let res = to_json_binary(&full_delegation_response)?;
850                Ok(res)
851            }
852            StakingQuery::AllValidators {} => Ok(to_json_binary(&AllValidatorsResponse::new(
853                self.get_validators(&staking_storage)?,
854            ))?),
855            StakingQuery::Validator { address } => Ok(to_json_binary(&ValidatorResponse::new(
856                self.get_validator(&staking_storage, &Addr::unchecked(address))?,
857            ))?),
858            q => bail!("Unsupported staking sudo message: {:?}", q),
859        }
860    }
861}
862
863#[derive(Default)]
864pub struct DistributionKeeper {}
865
866impl DistributionKeeper {
867    pub fn new() -> Self {
868        DistributionKeeper {}
869    }
870
871    /// Removes all rewards from the given (delegator, validator) pair and returns the amount
872    pub fn remove_rewards(
873        &self,
874        api: &dyn Api,
875        storage: &mut dyn Storage,
876        block: &BlockInfo,
877        delegator: &Addr,
878        validator: &Addr,
879    ) -> AnyResult<Uint128> {
880        let mut staking_storage = prefixed(storage, NAMESPACE_STAKING);
881        // update the validator and staker rewards
882        StakeKeeper::update_rewards(api, &mut staking_storage, block, validator)?;
883
884        // load updated rewards for delegator
885        let mut shares = STAKES.load(&staking_storage, (delegator, validator))?;
886        let rewards = Uint128::new(1).mul_floor(shares.rewards); // convert to Uint128
887
888        // remove rewards from delegator
889        shares.rewards = Decimal::zero();
890        STAKES.save(&mut staking_storage, (delegator, validator), &shares)?;
891
892        Ok(rewards)
893    }
894
895    pub fn get_withdraw_address(storage: &dyn Storage, delegator: &Addr) -> AnyResult<Addr> {
896        Ok(match WITHDRAW_ADDRESS.may_load(storage, delegator)? {
897            Some(a) => a,
898            None => delegator.clone(),
899        })
900    }
901
902    // https://docs.cosmos.network/main/modules/distribution#msgsetwithdrawaddress
903    pub fn set_withdraw_address(
904        storage: &mut dyn Storage,
905        delegator: &Addr,
906        withdraw_address: &Addr,
907    ) -> AnyResult<()> {
908        if delegator == withdraw_address {
909            WITHDRAW_ADDRESS.remove(storage, delegator);
910            Ok(())
911        } else {
912            // technically we should require that this address is not
913            // the address of a module. TODO: how?
914            WITHDRAW_ADDRESS
915                .save(storage, delegator, withdraw_address)
916                .map_err(|e| e.into())
917        }
918    }
919}
920
921impl Distribution for DistributionKeeper {}
922
923impl Module for DistributionKeeper {
924    type ExecT = DistributionMsg;
925    type QueryT = Empty;
926    type SudoT = Empty;
927
928    fn execute<ExecC, QueryC: CustomQuery>(
929        &self,
930        api: &dyn Api,
931        storage: &mut dyn Storage,
932        router: &dyn CosmosRouter<ExecC = ExecC, QueryC = QueryC>,
933        block: &BlockInfo,
934        sender: Addr,
935        msg: DistributionMsg,
936    ) -> AnyResult<AppResponse> {
937        match msg {
938            DistributionMsg::WithdrawDelegatorReward { validator } => {
939                let validator_addr = api.addr_validate(&validator)?;
940
941                let rewards = self.remove_rewards(api, storage, block, &sender, &validator_addr)?;
942
943                let staking_storage = prefixed_read(storage, NAMESPACE_STAKING);
944                let distribution_storage = prefixed_read(storage, NAMESPACE_DISTRIBUTION);
945                let staking_info = StakeKeeper::get_staking_info(&staking_storage)?;
946                let receiver = Self::get_withdraw_address(&distribution_storage, &sender)?;
947                // directly mint rewards to delegator
948                router.sudo(
949                    api,
950                    storage,
951                    block,
952                    BankSudo::Mint {
953                        to_address: receiver.into_string(),
954                        amount: vec![Coin {
955                            amount: rewards,
956                            denom: staking_info.bonded_denom.clone(),
957                        }],
958                    }
959                    .into(),
960                )?;
961
962                let events = vec![Event::new("withdraw_delegator_reward")
963                    .add_attribute("validator", &validator)
964                    .add_attribute("sender", &sender)
965                    .add_attribute(
966                        "amount",
967                        format!("{}{}", rewards, staking_info.bonded_denom),
968                    )];
969                Ok(AppResponse { events, data: None })
970            }
971            DistributionMsg::SetWithdrawAddress { address } => {
972                let address = api.addr_validate(&address)?;
973                // https://github.com/cosmos/cosmos-sdk/blob/4f6f6c00021f4b5ee486bbb71ae2071a8ceb47c9/x/distribution/keeper/msg_server.go#L38
974                let storage = &mut prefixed(storage, NAMESPACE_DISTRIBUTION);
975                Self::set_withdraw_address(storage, &sender, &address)?;
976                Ok(AppResponse {
977                    data: None,
978                    // https://github.com/cosmos/cosmos-sdk/blob/4f6f6c00021f4b5ee486bbb71ae2071a8ceb47c9/x/distribution/keeper/keeper.go#L74
979                    events: vec![Event::new("set_withdraw_address")
980                        .add_attribute("withdraw_address", address)],
981                })
982            }
983            m => bail!("Unsupported distribution message: {:?}", m),
984        }
985    }
986
987    fn sudo<ExecC, QueryC>(
988        &self,
989        _api: &dyn Api,
990        _storage: &mut dyn Storage,
991        _router: &dyn CosmosRouter<ExecC = ExecC, QueryC = QueryC>,
992        _block: &BlockInfo,
993        _msg: Empty,
994    ) -> AnyResult<AppResponse> {
995        bail!("Something went wrong - Distribution doesn't have sudo messages")
996    }
997
998    fn query(
999        &self,
1000        _api: &dyn Api,
1001        _storage: &dyn Storage,
1002        _querier: &dyn Querier,
1003        _block: &BlockInfo,
1004        _request: Empty,
1005    ) -> AnyResult<Binary> {
1006        bail!("Something went wrong - Distribution doesn't have query messages")
1007    }
1008}