casper_types/system/auction/
bid.rs

1// TODO - remove once schemars stops causing warning.
2#![allow(clippy::field_reassign_with_default)]
3
4mod vesting;
5
6use alloc::{collections::BTreeMap, vec::Vec};
7
8#[cfg(feature = "datasize")]
9use datasize::DataSize;
10#[cfg(feature = "json-schema")]
11use schemars::JsonSchema;
12use serde::{Deserialize, Serialize};
13
14use crate::{
15    bytesrepr::{self, FromBytes, ToBytes},
16    system::auction::{DelegationRate, Delegator, Error},
17    CLType, CLTyped, PublicKey, URef, U512,
18};
19
20pub use vesting::{VestingSchedule, VESTING_SCHEDULE_LENGTH_MILLIS};
21
22/// An entry in the validator map.
23#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Clone)]
24#[cfg_attr(feature = "datasize", derive(DataSize))]
25#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
26#[serde(deny_unknown_fields)]
27pub struct Bid {
28    /// Validator public key
29    validator_public_key: PublicKey,
30    /// The purse that was used for bonding.
31    bonding_purse: URef,
32    /// The amount of tokens staked by a validator (not including delegators).
33    staked_amount: U512,
34    /// Delegation rate
35    delegation_rate: DelegationRate,
36    /// Vesting schedule for a genesis validator. `None` if non-genesis validator.
37    vesting_schedule: Option<VestingSchedule>,
38    /// This validator's delegators, indexed by their public keys
39    delegators: BTreeMap<PublicKey, Delegator>,
40    /// `true` if validator has been "evicted"
41    inactive: bool,
42}
43
44impl Bid {
45    /// Creates new instance of a bid with locked funds.
46    pub fn locked(
47        validator_public_key: PublicKey,
48        bonding_purse: URef,
49        staked_amount: U512,
50        delegation_rate: DelegationRate,
51        release_timestamp_millis: u64,
52    ) -> Self {
53        let vesting_schedule = Some(VestingSchedule::new(release_timestamp_millis));
54        let delegators = BTreeMap::new();
55        let inactive = false;
56        Self {
57            validator_public_key,
58            bonding_purse,
59            staked_amount,
60            delegation_rate,
61            vesting_schedule,
62            delegators,
63            inactive,
64        }
65    }
66
67    /// Creates new instance of a bid with unlocked funds.
68    pub fn unlocked(
69        validator_public_key: PublicKey,
70        bonding_purse: URef,
71        staked_amount: U512,
72        delegation_rate: DelegationRate,
73    ) -> Self {
74        let vesting_schedule = None;
75        let delegators = BTreeMap::new();
76        let inactive = false;
77        Self {
78            validator_public_key,
79            bonding_purse,
80            staked_amount,
81            delegation_rate,
82            vesting_schedule,
83            delegators,
84            inactive,
85        }
86    }
87
88    /// Creates a new inactive instance of a bid with 0 staked amount.
89    pub fn empty(validator_public_key: PublicKey, bonding_purse: URef) -> Self {
90        let vesting_schedule = None;
91        let delegators = BTreeMap::new();
92        let inactive = true;
93        let staked_amount = 0.into();
94        let delegation_rate = Default::default();
95        Self {
96            validator_public_key,
97            bonding_purse,
98            staked_amount,
99            delegation_rate,
100            vesting_schedule,
101            delegators,
102            inactive,
103        }
104    }
105
106    /// Gets the validator public key of the provided bid
107    pub fn validator_public_key(&self) -> &PublicKey {
108        &self.validator_public_key
109    }
110
111    /// Gets the bonding purse of the provided bid
112    pub fn bonding_purse(&self) -> &URef {
113        &self.bonding_purse
114    }
115
116    /// Checks if a bid is still locked under a vesting schedule.
117    ///
118    /// Returns true if a timestamp falls below the initial lockup period + 91 days release
119    /// schedule, otherwise false.
120    pub fn is_locked(&self, timestamp_millis: u64) -> bool {
121        self.is_locked_with_vesting_schedule(timestamp_millis, VESTING_SCHEDULE_LENGTH_MILLIS)
122    }
123
124    /// Checks if a bid is still locked under a vesting schedule.
125    ///
126    /// Returns true if a timestamp falls below the initial lockup period + 91 days release
127    /// schedule, otherwise false.
128    pub fn is_locked_with_vesting_schedule(
129        &self,
130        timestamp_millis: u64,
131        vesting_schedule_period_millis: u64,
132    ) -> bool {
133        match &self.vesting_schedule {
134            Some(vesting_schedule) => {
135                vesting_schedule.is_vesting(timestamp_millis, vesting_schedule_period_millis)
136            }
137            None => false,
138        }
139    }
140
141    /// Gets the staked amount of the provided bid
142    pub fn staked_amount(&self) -> &U512 {
143        &self.staked_amount
144    }
145
146    /// Gets the staked amount of the provided bid
147    pub fn staked_amount_mut(&mut self) -> &mut U512 {
148        &mut self.staked_amount
149    }
150
151    /// Gets the delegation rate of the provided bid
152    pub fn delegation_rate(&self) -> &DelegationRate {
153        &self.delegation_rate
154    }
155
156    /// Returns a reference to the vesting schedule of the provided bid.  `None` if a non-genesis
157    /// validator.
158    pub fn vesting_schedule(&self) -> Option<&VestingSchedule> {
159        self.vesting_schedule.as_ref()
160    }
161
162    /// Returns a mutable reference to the vesting schedule of the provided bid.  `None` if a
163    /// non-genesis validator.
164    pub fn vesting_schedule_mut(&mut self) -> Option<&mut VestingSchedule> {
165        self.vesting_schedule.as_mut()
166    }
167
168    /// Returns a reference to the delegators of the provided bid
169    pub fn delegators(&self) -> &BTreeMap<PublicKey, Delegator> {
170        &self.delegators
171    }
172
173    /// Returns a mutable reference to the delegators of the provided bid
174    pub fn delegators_mut(&mut self) -> &mut BTreeMap<PublicKey, Delegator> {
175        &mut self.delegators
176    }
177
178    /// Returns `true` if validator is inactive
179    pub fn inactive(&self) -> bool {
180        self.inactive
181    }
182
183    /// Decreases the stake of the provided bid
184    pub fn decrease_stake(
185        &mut self,
186        amount: U512,
187        era_end_timestamp_millis: u64,
188    ) -> Result<U512, Error> {
189        let updated_staked_amount = self
190            .staked_amount
191            .checked_sub(amount)
192            .ok_or(Error::UnbondTooLarge)?;
193
194        let vesting_schedule = match self.vesting_schedule.as_ref() {
195            Some(vesting_schedule) => vesting_schedule,
196            None => {
197                self.staked_amount = updated_staked_amount;
198                return Ok(updated_staked_amount);
199            }
200        };
201
202        match vesting_schedule.locked_amount(era_end_timestamp_millis) {
203            Some(locked_amount) if updated_staked_amount < locked_amount => {
204                Err(Error::ValidatorFundsLocked)
205            }
206            None => {
207                // If `None`, then the locked amounts table has yet to be initialized (likely
208                // pre-90 day mark)
209                Err(Error::ValidatorFundsLocked)
210            }
211            Some(_) => {
212                self.staked_amount = updated_staked_amount;
213                Ok(updated_staked_amount)
214            }
215        }
216    }
217
218    /// Increases the stake of the provided bid
219    pub fn increase_stake(&mut self, amount: U512) -> Result<U512, Error> {
220        let updated_staked_amount = self
221            .staked_amount
222            .checked_add(amount)
223            .ok_or(Error::InvalidAmount)?;
224
225        self.staked_amount = updated_staked_amount;
226
227        Ok(updated_staked_amount)
228    }
229
230    /// Updates the delegation rate of the provided bid
231    pub fn with_delegation_rate(&mut self, delegation_rate: DelegationRate) -> &mut Self {
232        self.delegation_rate = delegation_rate;
233        self
234    }
235
236    /// Initializes the vesting schedule of provided bid if the provided timestamp is greater than
237    /// or equal to the bid's initial release timestamp and the bid is owned by a genesis
238    /// validator. This method initializes with default 14 week vesting schedule.
239    ///
240    /// Returns `true` if the provided bid's vesting schedule was initialized.
241    pub fn process(&mut self, timestamp_millis: u64) -> bool {
242        self.process_with_vesting_schedule(timestamp_millis, VESTING_SCHEDULE_LENGTH_MILLIS)
243    }
244
245    /// Initializes the vesting schedule of provided bid if the provided timestamp is greater than
246    /// or equal to the bid's initial release timestamp and the bid is owned by a genesis
247    /// validator.
248    ///
249    /// Returns `true` if the provided bid's vesting schedule was initialized.
250    pub fn process_with_vesting_schedule(
251        &mut self,
252        timestamp_millis: u64,
253        vesting_schedule_period_millis: u64,
254    ) -> bool {
255        // Put timestamp-sensitive processing logic in here
256        let staked_amount = self.staked_amount;
257        let vesting_schedule = match self.vesting_schedule_mut() {
258            Some(vesting_schedule) => vesting_schedule,
259            None => return false,
260        };
261        if timestamp_millis < vesting_schedule.initial_release_timestamp_millis() {
262            return false;
263        }
264
265        let mut initialized = false;
266
267        if vesting_schedule.initialize_with_schedule(staked_amount, vesting_schedule_period_millis)
268        {
269            initialized = true;
270        }
271
272        for delegator in self.delegators_mut().values_mut() {
273            let staked_amount = *delegator.staked_amount();
274            if let Some(vesting_schedule) = delegator.vesting_schedule_mut() {
275                if timestamp_millis >= vesting_schedule.initial_release_timestamp_millis()
276                    && vesting_schedule
277                        .initialize_with_schedule(staked_amount, vesting_schedule_period_millis)
278                {
279                    initialized = true;
280                }
281            }
282        }
283
284        initialized
285    }
286
287    /// Sets given bid's `inactive` field to `false`
288    pub fn activate(&mut self) -> bool {
289        self.inactive = false;
290        false
291    }
292
293    /// Sets given bid's `inactive` field to `true`
294    pub fn deactivate(&mut self) -> bool {
295        self.inactive = true;
296        true
297    }
298
299    /// Returns the total staked amount of validator + all delegators
300    pub fn total_staked_amount(&self) -> Result<U512, Error> {
301        self.delegators
302            .iter()
303            .try_fold(U512::zero(), |a, (_, b)| a.checked_add(*b.staked_amount()))
304            .and_then(|delegators_sum| delegators_sum.checked_add(*self.staked_amount()))
305            .ok_or(Error::InvalidAmount)
306    }
307}
308
309impl CLTyped for Bid {
310    fn cl_type() -> CLType {
311        CLType::Any
312    }
313}
314
315impl ToBytes for Bid {
316    fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
317        let mut result = bytesrepr::allocate_buffer(self)?;
318        self.validator_public_key.write_bytes(&mut result)?;
319        self.bonding_purse.write_bytes(&mut result)?;
320        self.staked_amount.write_bytes(&mut result)?;
321        self.delegation_rate.write_bytes(&mut result)?;
322        self.vesting_schedule.write_bytes(&mut result)?;
323        self.delegators().write_bytes(&mut result)?;
324        self.inactive.write_bytes(&mut result)?;
325        Ok(result)
326    }
327
328    fn serialized_length(&self) -> usize {
329        self.validator_public_key.serialized_length()
330            + self.bonding_purse.serialized_length()
331            + self.staked_amount.serialized_length()
332            + self.delegation_rate.serialized_length()
333            + self.vesting_schedule.serialized_length()
334            + self.delegators.serialized_length()
335            + self.inactive.serialized_length()
336    }
337
338    fn write_bytes(&self, writer: &mut Vec<u8>) -> Result<(), bytesrepr::Error> {
339        self.validator_public_key.write_bytes(writer)?;
340        self.bonding_purse.write_bytes(writer)?;
341        self.staked_amount.write_bytes(writer)?;
342        self.delegation_rate.write_bytes(writer)?;
343        self.vesting_schedule.write_bytes(writer)?;
344        self.delegators().write_bytes(writer)?;
345        self.inactive.write_bytes(writer)?;
346        Ok(())
347    }
348}
349
350impl FromBytes for Bid {
351    fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
352        let (validator_public_key, bytes) = FromBytes::from_bytes(bytes)?;
353        let (bonding_purse, bytes) = FromBytes::from_bytes(bytes)?;
354        let (staked_amount, bytes) = FromBytes::from_bytes(bytes)?;
355        let (delegation_rate, bytes) = FromBytes::from_bytes(bytes)?;
356        let (vesting_schedule, bytes) = FromBytes::from_bytes(bytes)?;
357        let (delegators, bytes) = FromBytes::from_bytes(bytes)?;
358        let (inactive, bytes) = FromBytes::from_bytes(bytes)?;
359        Ok((
360            Bid {
361                validator_public_key,
362                bonding_purse,
363                staked_amount,
364                delegation_rate,
365                vesting_schedule,
366                delegators,
367                inactive,
368            },
369            bytes,
370        ))
371    }
372}
373
374#[cfg(test)]
375mod tests {
376    use alloc::collections::BTreeMap;
377
378    use crate::{
379        bytesrepr,
380        system::auction::{bid::VestingSchedule, Bid, DelegationRate, Delegator},
381        AccessRights, PublicKey, SecretKey, URef, U512,
382    };
383
384    const WEEK_MILLIS: u64 = 7 * 24 * 60 * 60 * 1000;
385    const TEST_VESTING_SCHEDULE_LENGTH_MILLIS: u64 = 7 * WEEK_MILLIS;
386
387    #[test]
388    fn serialization_roundtrip() {
389        let founding_validator = Bid {
390            validator_public_key: PublicKey::from(
391                &SecretKey::ed25519_from_bytes([0u8; SecretKey::ED25519_LENGTH]).unwrap(),
392            ),
393            bonding_purse: URef::new([42; 32], AccessRights::READ_ADD_WRITE),
394            staked_amount: U512::one(),
395            delegation_rate: DelegationRate::max_value(),
396            vesting_schedule: Some(VestingSchedule::default()),
397            delegators: BTreeMap::default(),
398            inactive: true,
399        };
400        bytesrepr::test_serialization_roundtrip(&founding_validator);
401    }
402
403    #[test]
404    fn should_immediately_initialize_unlock_amounts() {
405        const TIMESTAMP_MILLIS: u64 = 0;
406
407        let validator_pk: PublicKey = (&SecretKey::ed25519_from_bytes([42; 32]).unwrap()).into();
408
409        let validator_release_timestamp = TIMESTAMP_MILLIS;
410        let vesting_schedule_period_millis = TIMESTAMP_MILLIS;
411        let validator_bonding_purse = URef::new([42; 32], AccessRights::ADD);
412        let validator_staked_amount = U512::from(1000);
413        let validator_delegation_rate = 0;
414
415        let mut bid = Bid::locked(
416            validator_pk,
417            validator_bonding_purse,
418            validator_staked_amount,
419            validator_delegation_rate,
420            validator_release_timestamp,
421        );
422
423        assert!(bid.process_with_vesting_schedule(
424            validator_release_timestamp,
425            vesting_schedule_period_millis,
426        ));
427        assert!(!bid.is_locked_with_vesting_schedule(
428            validator_release_timestamp,
429            vesting_schedule_period_millis
430        ));
431    }
432
433    #[test]
434    fn should_initialize_delegators_different_timestamps() {
435        const TIMESTAMP_MILLIS: u64 = WEEK_MILLIS;
436
437        let validator_pk: PublicKey = (&SecretKey::ed25519_from_bytes([42; 32]).unwrap()).into();
438
439        let delegator_1_pk: PublicKey = (&SecretKey::ed25519_from_bytes([43; 32]).unwrap()).into();
440        let delegator_2_pk: PublicKey = (&SecretKey::ed25519_from_bytes([44; 32]).unwrap()).into();
441
442        let validator_release_timestamp = TIMESTAMP_MILLIS;
443        let validator_bonding_purse = URef::new([42; 32], AccessRights::ADD);
444        let validator_staked_amount = U512::from(1000);
445        let validator_delegation_rate = 0;
446
447        let delegator_1_release_timestamp = TIMESTAMP_MILLIS + 1;
448        let delegator_1_bonding_purse = URef::new([52; 32], AccessRights::ADD);
449        let delegator_1_staked_amount = U512::from(2000);
450
451        let delegator_2_release_timestamp = TIMESTAMP_MILLIS + 2;
452        let delegator_2_bonding_purse = URef::new([62; 32], AccessRights::ADD);
453        let delegator_2_staked_amount = U512::from(3000);
454
455        let delegator_1 = Delegator::locked(
456            delegator_1_pk.clone(),
457            delegator_1_staked_amount,
458            delegator_1_bonding_purse,
459            validator_pk.clone(),
460            delegator_1_release_timestamp,
461        );
462
463        let delegator_2 = Delegator::locked(
464            delegator_2_pk.clone(),
465            delegator_2_staked_amount,
466            delegator_2_bonding_purse,
467            validator_pk.clone(),
468            delegator_2_release_timestamp,
469        );
470
471        let mut bid = Bid::locked(
472            validator_pk,
473            validator_bonding_purse,
474            validator_staked_amount,
475            validator_delegation_rate,
476            validator_release_timestamp,
477        );
478
479        assert!(!bid.process_with_vesting_schedule(
480            validator_release_timestamp - 1,
481            TEST_VESTING_SCHEDULE_LENGTH_MILLIS
482        ));
483
484        {
485            let delegators = bid.delegators_mut();
486
487            delegators.insert(delegator_1_pk.clone(), delegator_1);
488            delegators.insert(delegator_2_pk.clone(), delegator_2);
489        }
490
491        assert!(bid.process_with_vesting_schedule(
492            delegator_1_release_timestamp,
493            TEST_VESTING_SCHEDULE_LENGTH_MILLIS
494        ));
495
496        let delegator_1_updated_1 = bid.delegators().get(&delegator_1_pk).cloned().unwrap();
497        assert!(delegator_1_updated_1
498            .vesting_schedule()
499            .unwrap()
500            .locked_amounts()
501            .is_some());
502
503        let delegator_2_updated_1 = bid.delegators().get(&delegator_2_pk).cloned().unwrap();
504        assert!(delegator_2_updated_1
505            .vesting_schedule()
506            .unwrap()
507            .locked_amounts()
508            .is_none());
509
510        assert!(bid.process_with_vesting_schedule(
511            delegator_2_release_timestamp,
512            TEST_VESTING_SCHEDULE_LENGTH_MILLIS
513        ));
514
515        let delegator_1_updated_2 = bid.delegators().get(&delegator_1_pk).cloned().unwrap();
516        assert!(delegator_1_updated_2
517            .vesting_schedule()
518            .unwrap()
519            .locked_amounts()
520            .is_some());
521        // Delegator 1 is already initialized and did not change after 2nd Bid::process
522        assert_eq!(delegator_1_updated_1, delegator_1_updated_2);
523
524        let delegator_2_updated_2 = bid.delegators().get(&delegator_2_pk).cloned().unwrap();
525        assert!(delegator_2_updated_2
526            .vesting_schedule()
527            .unwrap()
528            .locked_amounts()
529            .is_some());
530
531        // Delegator 2 is different compared to first Bid::process
532        assert_ne!(delegator_2_updated_1, delegator_2_updated_2);
533
534        // Validator initialized, and all delegators initialized
535        assert!(!bid.process_with_vesting_schedule(
536            delegator_2_release_timestamp + 1,
537            TEST_VESTING_SCHEDULE_LENGTH_MILLIS
538        ));
539    }
540}
541
542#[cfg(test)]
543mod prop_tests {
544    use proptest::prelude::*;
545
546    use crate::{bytesrepr, gens};
547
548    proptest! {
549        #[test]
550        fn test_value_bid(bid in gens::bid_arb(1..100)) {
551            bytesrepr::test_serialization_roundtrip(&bid);
552        }
553    }
554}