#![allow(clippy::field_reassign_with_default)]
mod vesting;
use alloc::{collections::BTreeMap, vec::Vec};
#[cfg(feature = "std")]
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::{
bytesrepr::{self, FromBytes, ToBytes},
system::auction::{DelegationRate, Delegator, Error},
CLType, CLTyped, PublicKey, URef, U512,
};
pub use vesting::VestingSchedule;
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "std", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct Bid {
validator_public_key: PublicKey,
bonding_purse: URef,
staked_amount: U512,
delegation_rate: DelegationRate,
vesting_schedule: Option<VestingSchedule>,
delegators: BTreeMap<PublicKey, Delegator>,
inactive: bool,
}
impl Bid {
pub fn locked(
validator_public_key: PublicKey,
bonding_purse: URef,
staked_amount: U512,
delegation_rate: DelegationRate,
release_timestamp_millis: u64,
) -> Self {
let vesting_schedule = Some(VestingSchedule::new(release_timestamp_millis));
let delegators = BTreeMap::new();
let inactive = false;
Self {
validator_public_key,
bonding_purse,
staked_amount,
delegation_rate,
vesting_schedule,
delegators,
inactive,
}
}
pub fn unlocked(
validator_public_key: PublicKey,
bonding_purse: URef,
staked_amount: U512,
delegation_rate: DelegationRate,
) -> Self {
let vesting_schedule = None;
let delegators = BTreeMap::new();
let inactive = false;
Self {
validator_public_key,
bonding_purse,
staked_amount,
delegation_rate,
vesting_schedule,
delegators,
inactive,
}
}
pub fn validator_public_key(&self) -> &PublicKey {
&self.validator_public_key
}
pub fn bonding_purse(&self) -> &URef {
&self.bonding_purse
}
pub fn staked_amount(&self) -> &U512 {
&self.staked_amount
}
pub fn staked_amount_mut(&mut self) -> &mut U512 {
&mut self.staked_amount
}
pub fn delegation_rate(&self) -> &DelegationRate {
&self.delegation_rate
}
pub fn vesting_schedule(&self) -> Option<&VestingSchedule> {
self.vesting_schedule.as_ref()
}
pub fn vesting_schedule_mut(&mut self) -> Option<&mut VestingSchedule> {
self.vesting_schedule.as_mut()
}
pub fn delegators(&self) -> &BTreeMap<PublicKey, Delegator> {
&self.delegators
}
pub fn delegators_mut(&mut self) -> &mut BTreeMap<PublicKey, Delegator> {
&mut self.delegators
}
pub fn inactive(&self) -> bool {
self.inactive
}
pub fn decrease_stake(
&mut self,
amount: U512,
era_end_timestamp_millis: u64,
) -> Result<U512, Error> {
let updated_staked_amount = self
.staked_amount
.checked_sub(amount)
.ok_or(Error::UnbondTooLarge)?;
let vesting_schedule = match self.vesting_schedule.as_ref() {
Some(vesting_sechdule) => vesting_sechdule,
None => {
self.staked_amount = updated_staked_amount;
return Ok(updated_staked_amount);
}
};
match vesting_schedule.locked_amount(era_end_timestamp_millis) {
Some(locked_amount) if updated_staked_amount < locked_amount => {
Err(Error::ValidatorFundsLocked)
}
None => {
Err(Error::ValidatorFundsLocked)
}
Some(_) => {
self.staked_amount = updated_staked_amount;
Ok(updated_staked_amount)
}
}
}
pub fn increase_stake(&mut self, amount: U512) -> Result<U512, Error> {
let updated_staked_amount = self
.staked_amount
.checked_add(amount)
.ok_or(Error::InvalidAmount)?;
self.staked_amount = updated_staked_amount;
Ok(updated_staked_amount)
}
pub fn with_delegation_rate(&mut self, delegation_rate: DelegationRate) -> &mut Self {
self.delegation_rate = delegation_rate;
self
}
pub fn process(&mut self, timestamp_millis: u64) -> bool {
let staked_amount = self.staked_amount;
let vesting_schedule = match self.vesting_schedule_mut() {
Some(vesting_schedule) => vesting_schedule,
None => return false,
};
if timestamp_millis < vesting_schedule.initial_release_timestamp_millis() {
return false;
}
let mut initialized = false;
if vesting_schedule.initialize(staked_amount) {
initialized = true;
}
for delegator in self.delegators_mut().values_mut() {
let staked_amount = *delegator.staked_amount();
if let Some(vesting_schedule) = delegator.vesting_schedule_mut() {
if timestamp_millis >= vesting_schedule.initial_release_timestamp_millis()
&& vesting_schedule.initialize(staked_amount)
{
initialized = true;
}
}
}
initialized
}
pub fn activate(&mut self) -> bool {
self.inactive = false;
false
}
pub fn deactivate(&mut self) -> bool {
self.inactive = true;
true
}
pub fn total_staked_amount(&self) -> Result<U512, Error> {
self.delegators
.iter()
.fold(Some(U512::zero()), |maybe_a, (_, b)| {
maybe_a.and_then(|a| a.checked_add(*b.staked_amount()))
})
.and_then(|delegators_sum| delegators_sum.checked_add(*self.staked_amount()))
.ok_or(Error::InvalidAmount)
}
}
impl CLTyped for Bid {
fn cl_type() -> CLType {
CLType::Any
}
}
impl ToBytes for Bid {
fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
let mut result = bytesrepr::allocate_buffer(self)?;
result.extend(self.validator_public_key.to_bytes()?);
result.extend(self.bonding_purse.to_bytes()?);
result.extend(self.staked_amount.to_bytes()?);
result.extend(self.delegation_rate.to_bytes()?);
result.extend(self.vesting_schedule.to_bytes()?);
result.extend(self.delegators.to_bytes()?);
result.extend(self.inactive.to_bytes()?);
Ok(result)
}
fn serialized_length(&self) -> usize {
self.validator_public_key.serialized_length()
+ self.bonding_purse.serialized_length()
+ self.staked_amount.serialized_length()
+ self.delegation_rate.serialized_length()
+ self.vesting_schedule.serialized_length()
+ self.delegators.serialized_length()
+ self.inactive.serialized_length()
}
}
impl FromBytes for Bid {
fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
let (validator_public_key, bytes) = FromBytes::from_bytes(bytes)?;
let (bonding_purse, bytes) = FromBytes::from_bytes(bytes)?;
let (staked_amount, bytes) = FromBytes::from_bytes(bytes)?;
let (delegation_rate, bytes) = FromBytes::from_bytes(bytes)?;
let (vesting_schedule, bytes) = FromBytes::from_bytes(bytes)?;
let (delegators, bytes) = FromBytes::from_bytes(bytes)?;
let (inactive, bytes) = FromBytes::from_bytes(bytes)?;
Ok((
Bid {
validator_public_key,
bonding_purse,
staked_amount,
delegation_rate,
vesting_schedule,
delegators,
inactive,
},
bytes,
))
}
}
#[cfg(test)]
mod tests {
use alloc::collections::BTreeMap;
use crate::{
bytesrepr,
system::auction::{bid::VestingSchedule, Bid, DelegationRate, Delegator},
AccessRights, PublicKey, SecretKey, URef, U512,
};
#[test]
fn serialization_roundtrip() {
let founding_validator = Bid {
validator_public_key: PublicKey::from(SecretKey::ed25519(
[0u8; SecretKey::ED25519_LENGTH],
)),
bonding_purse: URef::new([42; 32], AccessRights::READ_ADD_WRITE),
staked_amount: U512::one(),
delegation_rate: DelegationRate::max_value(),
vesting_schedule: Some(VestingSchedule::default()),
delegators: BTreeMap::default(),
inactive: true,
};
bytesrepr::test_serialization_roundtrip(&founding_validator);
}
#[test]
fn should_initialize_delegators_different_timestamps() {
const WEEK_MILLIS: u64 = 7 * 24 * 60 * 60 * 1000;
const TIMESTAMP_MILLIS: u64 = WEEK_MILLIS as u64;
let validator_pk = SecretKey::ed25519([42; 32]).into();
let delegator_1_pk = SecretKey::ed25519([43; 32]).into();
let delegator_2_pk = SecretKey::ed25519([44; 32]).into();
let validator_release_timestamp = TIMESTAMP_MILLIS;
let validator_bonding_purse = URef::new([42; 32], AccessRights::ADD);
let validator_staked_amount = U512::from(1000);
let validator_delegation_rate = 0;
let delegator_1_release_timestamp = TIMESTAMP_MILLIS + 1;
let delegator_1_bonding_purse = URef::new([52; 32], AccessRights::ADD);
let delegator_1_staked_amount = U512::from(2000);
let delegator_2_release_timestamp = TIMESTAMP_MILLIS + 2;
let delegator_2_bonding_purse = URef::new([62; 32], AccessRights::ADD);
let delegator_2_staked_amount = U512::from(3000);
let delegator_1 = Delegator::locked(
delegator_1_pk,
delegator_1_staked_amount,
delegator_1_bonding_purse,
validator_pk,
delegator_1_release_timestamp,
);
let delegator_2 = Delegator::locked(
delegator_2_pk,
delegator_2_staked_amount,
delegator_2_bonding_purse,
validator_pk,
delegator_2_release_timestamp,
);
let mut bid = Bid::locked(
validator_pk,
validator_bonding_purse,
validator_staked_amount,
validator_delegation_rate,
validator_release_timestamp,
);
assert!(!bid.process(validator_release_timestamp - 1));
{
let delegators = bid.delegators_mut();
delegators.insert(delegator_1_pk, delegator_1);
delegators.insert(delegator_2_pk, delegator_2);
}
assert!(bid.process(delegator_1_release_timestamp));
let delegator_1_updated_1 = bid.delegators().get(&delegator_1_pk).cloned().unwrap();
assert!(delegator_1_updated_1
.vesting_schedule()
.unwrap()
.locked_amounts()
.is_some());
let delegator_2_updated_1 = bid.delegators().get(&delegator_2_pk).cloned().unwrap();
assert!(delegator_2_updated_1
.vesting_schedule()
.unwrap()
.locked_amounts()
.is_none());
assert!(bid.process(delegator_2_release_timestamp));
let delegator_1_updated_2 = bid.delegators().get(&delegator_1_pk).cloned().unwrap();
assert!(delegator_1_updated_2
.vesting_schedule()
.unwrap()
.locked_amounts()
.is_some());
assert_eq!(delegator_1_updated_1, delegator_1_updated_2);
let delegator_2_updated_2 = bid.delegators().get(&delegator_2_pk).cloned().unwrap();
assert!(delegator_2_updated_2
.vesting_schedule()
.unwrap()
.locked_amounts()
.is_some());
assert_ne!(delegator_2_updated_1, delegator_2_updated_2);
assert!(!bid.process(delegator_2_release_timestamp + 1));
}
}