1use cosmwasm_schema::{cw_serde, QueryResponses};
2use cosmwasm_std::{to_json_binary, Binary, StdResult, Storage, Timestamp, Uint128};
3use cw_wormhole::Wormhole;
4
5#[cfg(test)]
6mod tests;
7
8pub struct StakeTracker<'a> {
9 total_staked: Wormhole<'a, (), Uint128>,
12 validators: Wormhole<'a, String, Uint128>,
23 cardinality: Wormhole<'a, (), u64>,
26}
27
28#[cw_serde]
29#[derive(QueryResponses)]
30pub enum StakeTrackerQuery {
31 #[returns(::cosmwasm_std::Uint128)]
32 Cardinality { t: Timestamp },
33 #[returns(::cosmwasm_std::Uint128)]
34 TotalStaked { t: Timestamp },
35 #[returns(::cosmwasm_std::Uint128)]
36 ValidatorStaked { validator: String, t: Timestamp },
37}
38
39impl<'a> StakeTracker<'a> {
40 pub const fn new(
41 staked_prefix: &'a str,
42 validator_prefix: &'a str,
43 cardinality_prefix: &'a str,
44 ) -> Self {
45 Self {
46 total_staked: Wormhole::new(staked_prefix),
47 validators: Wormhole::new(validator_prefix),
48 cardinality: Wormhole::new(cardinality_prefix),
49 }
50 }
51
52 pub fn on_delegate(
53 &self,
54 storage: &mut dyn Storage,
55 t: Timestamp,
56 validator: String,
57 amount: Uint128,
58 ) -> StdResult<()> {
59 self.total_staked
60 .increment(storage, (), t.seconds(), amount)?;
61 let old = self
62 .validators
63 .load(storage, validator.clone(), t.seconds())?
64 .unwrap_or_default();
65 if old.is_zero() && !amount.is_zero() {
66 self.cardinality.increment(storage, (), t.seconds(), 1)?;
67 }
68 self.validators
69 .increment(storage, validator, t.seconds(), amount)?;
70 Ok(())
71 }
72
73 pub fn on_redelegate(
79 &self,
80 storage: &mut dyn Storage,
81 t: Timestamp,
82 src: String,
83 dst: String,
84 amount: Uint128,
85 ) -> StdResult<()> {
86 let new = self
87 .validators
88 .decrement(storage, src, t.seconds(), amount)?;
89 if new.is_zero() {
90 self.cardinality.decrement(storage, (), t.seconds(), 1)?;
91 }
92 let new = self
93 .validators
94 .increment(storage, dst, t.seconds(), amount)?;
95 if new == amount {
96 self.cardinality.increment(storage, (), t.seconds(), 1)?;
97 }
98 Ok(())
99 }
100
101 pub fn on_undelegate(
102 &self,
103 storage: &mut dyn Storage,
104 t: Timestamp,
105 validator: String,
106 amount: Uint128,
107 unbonding_duration_seconds: u64,
108 ) -> StdResult<()> {
109 self.total_staked.decrement(
110 storage,
111 (),
112 t.seconds() + unbonding_duration_seconds,
113 amount,
114 )?;
115 let new = self.validators.decrement(
116 storage,
117 validator,
118 t.seconds() + unbonding_duration_seconds,
119 amount,
120 )?;
121 if new.is_zero() && !amount.is_zero() {
122 self.cardinality
123 .decrement(storage, (), t.seconds() + unbonding_duration_seconds, 1)?;
124 }
125 Ok(())
126 }
127
128 pub fn on_bonded_slash(
137 &self,
138 storage: &mut dyn Storage,
139 t: Timestamp,
140 validator: String,
141 amount: Uint128,
142 ) -> StdResult<()> {
143 enum Change {
144 Inc(u64),
146 Dec(u64),
148 }
149
150 self.total_staked
151 .decrement(storage, (), t.seconds(), amount)?;
152
153 let mut was_nonzero = true;
157 let mut cardinality_changes = vec![];
160
161 self.validators
165 .update(storage, validator, t.seconds(), &mut |staked, time| {
166 let new = staked - amount;
167 if new.is_zero() && was_nonzero {
168 cardinality_changes.push(Change::Dec(time));
171 was_nonzero = false;
172 } else if !new.is_zero() && !was_nonzero {
173 cardinality_changes.push(Change::Inc(time));
177 was_nonzero = true;
178 }
179 new
180 })?;
181
182 for change in cardinality_changes {
186 match change {
187 Change::Inc(time) => self.cardinality.increment(storage, (), time, 1)?,
188 Change::Dec(time) => self.cardinality.decrement(storage, (), time, 1)?,
189 };
190 }
191
192 Ok(())
193 }
194
195 pub fn on_unbonding_slash(
204 &self,
205 storage: &mut dyn Storage,
206 t: Timestamp,
207 validator: String,
208 amount: Uint128,
209 ) -> StdResult<()> {
210 self.total_staked
219 .dangerously_update(storage, (), t.seconds(), &mut |v, _| v - amount)?;
220 let new =
221 self.validators
222 .dangerously_update(storage, validator, t.seconds(), &mut |v, _| v - amount)?;
223 if new.is_zero() {
224 self.cardinality
225 .dangerously_update(storage, (), t.seconds(), &mut |v, _| v - 1)?;
226 }
227 Ok(())
228 }
229
230 pub fn total_staked(&self, storage: &dyn Storage, t: Timestamp) -> StdResult<Uint128> {
233 self.total_staked
234 .load(storage, (), t.seconds())
235 .map(|v| v.unwrap_or_default())
236 }
237
238 pub fn validator_staked(
241 &self,
242 storage: &dyn Storage,
243 t: Timestamp,
244 v: String,
245 ) -> StdResult<Uint128> {
246 self.validators
247 .load(storage, v, t.seconds())
248 .map(|v| v.unwrap_or_default())
249 }
250
251 pub fn validator_cardinality(&self, storage: &dyn Storage, t: Timestamp) -> StdResult<u64> {
254 self.cardinality
255 .load(storage, (), t.seconds())
256 .map(|v| v.unwrap_or_default())
257 }
258
259 pub fn query(&self, storage: &dyn Storage, msg: StakeTrackerQuery) -> StdResult<Binary> {
263 match msg {
264 StakeTrackerQuery::Cardinality { t } => to_json_binary(&Uint128::new(
265 self.validator_cardinality(storage, t)?.into(),
266 )),
267 StakeTrackerQuery::TotalStaked { t } => to_json_binary(&self.total_staked(storage, t)?),
268 StakeTrackerQuery::ValidatorStaked { validator, t } => {
269 to_json_binary(&self.validator_staked(storage, t, validator)?)
270 }
271 }
272 }
273}