casper_types/system/auction/
bid.rs

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