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#[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: PublicKey,
33 bonding_purse: URef,
35 staked_amount: U512,
37 delegation_rate: DelegationRate,
39 vesting_schedule: Option<VestingSchedule>,
41 #[serde(with = "BTreeMapToArray::<PublicKey, Delegator, DelegatorLabels>")]
43 delegators: BTreeMap<PublicKey, Delegator>,
44 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 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 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 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 pub fn validator_public_key(&self) -> &PublicKey {
140 &self.validator_public_key
141 }
142
143 pub fn bonding_purse(&self) -> &URef {
145 &self.bonding_purse
146 }
147
148 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 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 pub fn staked_amount(&self) -> &U512 {
175 &self.staked_amount
176 }
177
178 pub fn staked_amount_mut(&mut self) -> &mut U512 {
180 &mut self.staked_amount
181 }
182
183 pub fn delegation_rate(&self) -> &DelegationRate {
185 &self.delegation_rate
186 }
187
188 pub fn vesting_schedule(&self) -> Option<&VestingSchedule> {
191 self.vesting_schedule.as_ref()
192 }
193
194 pub fn vesting_schedule_mut(&mut self) -> Option<&mut VestingSchedule> {
197 self.vesting_schedule.as_mut()
198 }
199
200 pub fn delegators(&self) -> &BTreeMap<PublicKey, Delegator> {
202 &self.delegators
203 }
204
205 pub fn delegators_mut(&mut self) -> &mut BTreeMap<PublicKey, Delegator> {
207 &mut self.delegators
208 }
209
210 pub fn inactive(&self) -> bool {
212 self.inactive
213 }
214
215 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 Err(Error::ValidatorFundsLocked)
242 }
243 Some(_) => {
244 self.staked_amount = updated_staked_amount;
245 Ok(updated_staked_amount)
246 }
247 }
248 }
249
250 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 pub fn with_delegation_rate(&mut self, delegation_rate: DelegationRate) -> &mut Self {
264 self.delegation_rate = delegation_rate;
265 self
266 }
267
268 pub fn process(&mut self, timestamp_millis: u64) -> bool {
274 self.process_with_vesting_schedule(timestamp_millis, VESTING_SCHEDULE_LENGTH_MILLIS)
275 }
276
277 pub fn process_with_vesting_schedule(
283 &mut self,
284 timestamp_millis: u64,
285 vesting_schedule_period_millis: u64,
286 ) -> bool {
287 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 pub fn activate(&mut self) -> bool {
321 self.inactive = false;
322 false
323 }
324
325 pub fn deactivate(&mut self) -> bool {
327 self.inactive = true;
328 true
329 }
330
331 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 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 assert_ne!(delegator_2_updated_1, delegator_2_updated_2);
618
619 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}