1#![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#[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: PublicKey,
30 bonding_purse: URef,
32 staked_amount: U512,
34 delegation_rate: DelegationRate,
36 vesting_schedule: Option<VestingSchedule>,
38 delegators: BTreeMap<PublicKey, Delegator>,
40 inactive: bool,
42}
43
44impl Bid {
45 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 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 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 pub fn validator_public_key(&self) -> &PublicKey {
108 &self.validator_public_key
109 }
110
111 pub fn bonding_purse(&self) -> &URef {
113 &self.bonding_purse
114 }
115
116 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 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 pub fn staked_amount(&self) -> &U512 {
143 &self.staked_amount
144 }
145
146 pub fn staked_amount_mut(&mut self) -> &mut U512 {
148 &mut self.staked_amount
149 }
150
151 pub fn delegation_rate(&self) -> &DelegationRate {
153 &self.delegation_rate
154 }
155
156 pub fn vesting_schedule(&self) -> Option<&VestingSchedule> {
159 self.vesting_schedule.as_ref()
160 }
161
162 pub fn vesting_schedule_mut(&mut self) -> Option<&mut VestingSchedule> {
165 self.vesting_schedule.as_mut()
166 }
167
168 pub fn delegators(&self) -> &BTreeMap<PublicKey, Delegator> {
170 &self.delegators
171 }
172
173 pub fn delegators_mut(&mut self) -> &mut BTreeMap<PublicKey, Delegator> {
175 &mut self.delegators
176 }
177
178 pub fn inactive(&self) -> bool {
180 self.inactive
181 }
182
183 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 Err(Error::ValidatorFundsLocked)
210 }
211 Some(_) => {
212 self.staked_amount = updated_staked_amount;
213 Ok(updated_staked_amount)
214 }
215 }
216 }
217
218 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 pub fn with_delegation_rate(&mut self, delegation_rate: DelegationRate) -> &mut Self {
232 self.delegation_rate = delegation_rate;
233 self
234 }
235
236 pub fn process(&mut self, timestamp_millis: u64) -> bool {
242 self.process_with_vesting_schedule(timestamp_millis, VESTING_SCHEDULE_LENGTH_MILLIS)
243 }
244
245 pub fn process_with_vesting_schedule(
251 &mut self,
252 timestamp_millis: u64,
253 vesting_schedule_period_millis: u64,
254 ) -> bool {
255 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 pub fn activate(&mut self) -> bool {
289 self.inactive = false;
290 false
291 }
292
293 pub fn deactivate(&mut self) -> bool {
295 self.inactive = true;
296 true
297 }
298
299 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 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 assert_ne!(delegator_2_updated_1, delegator_2_updated_2);
533
534 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}