casper_storage/system/
auction.rs

1mod auction_native;
2/// Auction business logic.
3pub mod detail;
4/// System logic providers.
5pub mod providers;
6
7use itertools::Itertools;
8use num_rational::Ratio;
9use std::collections::BTreeMap;
10use tracing::{debug, error, warn};
11
12use self::providers::{AccountProvider, MintProvider, RuntimeProvider, StorageProvider};
13use crate::system::auction::detail::{
14    process_undelegation, process_updated_delegator_reservation_slots,
15    process_updated_delegator_stake_boundaries, process_with_vesting_schedule, read_delegator_bids,
16    read_validator_bid, rewards_per_validator, seigniorage_recipients, DistributeTarget,
17};
18use casper_types::{
19    account::AccountHash,
20    system::auction::{
21        BidAddr, BidKind, Bridge, DelegationRate, DelegatorKind, EraInfo, EraValidators, Error,
22        Reservation, SeigniorageAllocation, SeigniorageRecipientsSnapshot, SeigniorageRecipientsV2,
23        UnbondEra, UnbondKind, ValidatorBid, ValidatorCredit, ValidatorWeights,
24        DELEGATION_RATE_DENOMINATOR,
25    },
26    AccessRights, ApiError, EraId, Key, PublicKey, URef, U512,
27};
28
29/// Bonding auction contract interface
30pub trait Auction:
31    StorageProvider + RuntimeProvider + MintProvider + AccountProvider + Sized
32{
33    /// Returns active validators and auction winners for a number of future eras determined by the
34    /// configured auction_delay.
35    fn get_era_validators(&mut self) -> Result<EraValidators, Error> {
36        let snapshot = detail::get_seigniorage_recipients_snapshot(self)?;
37        let era_validators = detail::era_validators_from_snapshot(snapshot);
38        Ok(era_validators)
39    }
40
41    /// Returns validators in era_validators, mapped to their bids or founding stakes, delegation
42    /// rates and lists of delegators together with their delegated quantities from delegators.
43    /// This function is publicly accessible, but intended for system use by the Handle Payment
44    /// contract, because this data is necessary for distributing seigniorage.
45    fn read_seigniorage_recipients(&mut self) -> Result<SeigniorageRecipientsV2, Error> {
46        // `era_validators` are assumed to be computed already by calling "run_auction" entrypoint.
47        let era_index = detail::get_era_id(self)?;
48        let mut seigniorage_recipients_snapshot =
49            detail::get_seigniorage_recipients_snapshot(self)?;
50        let seigniorage_recipients = seigniorage_recipients_snapshot
51            .remove(&era_index)
52            .ok_or(Error::MissingSeigniorageRecipients)?;
53        Ok(seigniorage_recipients)
54    }
55
56    /// This entry point adds or modifies an entry in the `Key::Bid` section of the global state and
57    /// creates (or tops off) a bid purse. Post genesis, any new call on this entry point causes a
58    /// non-founding validator in the system to exist.
59    ///
60    /// The logic works for both founding and non-founding validators, making it possible to adjust
61    /// their delegation rate and increase their stakes.
62    ///
63    /// A validator with its bid inactive due to slashing can activate its bid again by increasing
64    /// its stake.
65    ///
66    /// Validators cannot create a bid with 0 amount, and the delegation rate can't exceed
67    /// [`DELEGATION_RATE_DENOMINATOR`].
68    ///
69    /// Returns a [`U512`] value indicating total amount of tokens staked for given `public_key`.
70    #[allow(clippy::too_many_arguments)]
71    fn add_bid(
72        &mut self,
73        public_key: PublicKey,
74        delegation_rate: DelegationRate,
75        amount: U512,
76        minimum_delegation_amount: u64,
77        maximum_delegation_amount: u64,
78        minimum_bid_amount: u64,
79        max_delegators_per_validator: u32,
80        reserved_slots: u32,
81    ) -> Result<U512, ApiError> {
82        if !self.allow_auction_bids() {
83            // The validator set may be closed on some side chains,
84            // which is configured by disabling bids.
85            return Err(Error::AuctionBidsDisabled.into());
86        }
87
88        if amount == U512::zero() {
89            return Err(Error::BondTooSmall.into());
90        }
91
92        if delegation_rate > DELEGATION_RATE_DENOMINATOR {
93            return Err(Error::DelegationRateTooLarge.into());
94        }
95
96        if reserved_slots > max_delegators_per_validator {
97            return Err(Error::ExceededReservationSlotsLimit.into());
98        }
99
100        let provided_account_hash = AccountHash::from(&public_key);
101
102        if !self.is_allowed_session_caller(&provided_account_hash) {
103            return Err(Error::InvalidContext.into());
104        }
105        let validator_bid_key = BidAddr::from(public_key.clone()).into();
106        let (target, validator_bid) = if let Some(BidKind::Validator(mut validator_bid)) =
107            self.read_bid(&validator_bid_key)?
108        {
109            let updated_stake = validator_bid.increase_stake(amount)?;
110            if updated_stake < U512::from(minimum_bid_amount) {
111                return Err(Error::BondTooSmall.into());
112            }
113            // idempotent
114            validator_bid.activate();
115
116            validator_bid.with_delegation_rate(delegation_rate);
117            process_updated_delegator_stake_boundaries(
118                self,
119                &mut validator_bid,
120                minimum_delegation_amount,
121                maximum_delegation_amount,
122            )?;
123            process_updated_delegator_reservation_slots(
124                self,
125                &mut validator_bid,
126                max_delegators_per_validator,
127                reserved_slots,
128            )?;
129            (*validator_bid.bonding_purse(), validator_bid)
130        } else {
131            if amount < U512::from(minimum_bid_amount) {
132                return Err(Error::BondTooSmall.into());
133            }
134            // create new validator bid
135            let bonding_purse = self.create_purse()?;
136            let validator_bid = ValidatorBid::unlocked(
137                public_key,
138                bonding_purse,
139                amount,
140                delegation_rate,
141                minimum_delegation_amount,
142                maximum_delegation_amount,
143                reserved_slots,
144            );
145            (bonding_purse, Box::new(validator_bid))
146        };
147
148        let source = self.get_main_purse()?;
149        self.mint_transfer_direct(
150            Some(PublicKey::System.to_account_hash()),
151            source,
152            target,
153            amount,
154            None,
155        )
156        .map_err(|_| Error::TransferToBidPurse)?
157        .map_err(|mint_error| {
158            // Propagate mint contract's error that occurred during execution of transfer
159            // entrypoint. This will improve UX in case of (for example)
160            // unapproved spending limit error.
161            ApiError::from(mint_error)
162        })?;
163
164        let updated_amount = validator_bid.staked_amount();
165        self.write_bid(validator_bid_key, BidKind::Validator(validator_bid))?;
166        Ok(updated_amount)
167    }
168
169    /// Unbonds aka reduces stake by specified amount, adding an entry to the unbonding queue.
170    /// For a genesis validator, this is subject to vesting if applicable to a given network.
171    ///
172    /// If this bid stake is reduced to 0, any delegators to this bid will be undelegated, with
173    /// entries made to the unbonding queue for each of them for their full delegated amount.
174    /// Additionally, this bid record will be pruned away from the next calculated root hash.
175    ///
176    /// An attempt to reduce stake by more than is staked will instead 0 the stake.
177    ///
178    /// The function returns the remaining staked amount (we allow partial unbonding).
179    fn withdraw_bid(
180        &mut self,
181        public_key: PublicKey,
182        amount: U512,
183        minimum_bid_amount: u64,
184    ) -> Result<U512, Error> {
185        let provided_account_hash = AccountHash::from(&public_key);
186
187        if !self.is_allowed_session_caller(&provided_account_hash) {
188            return Err(Error::InvalidContext);
189        }
190
191        let validator_bid_addr = BidAddr::from(public_key.clone());
192        let validator_bid_key = validator_bid_addr.into();
193        let mut validator_bid = read_validator_bid(self, &validator_bid_key)?;
194        let staked_amount = validator_bid.staked_amount();
195
196        // An attempt to unbond more than is staked results in unbonding the staked amount.
197        let unbonding_amount = U512::min(amount, validator_bid.staked_amount());
198
199        let era_end_timestamp_millis = detail::get_era_end_timestamp_millis(self)?;
200        let updated_stake =
201            validator_bid.decrease_stake(unbonding_amount, era_end_timestamp_millis)?;
202
203        debug!(
204            "withdrawing bid for {validator_bid_addr} reducing {staked_amount} by {unbonding_amount} to {updated_stake}",
205        );
206        // if validator stake is less than minimum_bid_amount, unbond fully and prune validator bid
207        if updated_stake < U512::from(minimum_bid_amount) {
208            // create unbonding purse for full validator stake
209            detail::create_unbonding_purse(
210                self,
211                public_key.clone(),
212                UnbondKind::Validator(public_key.clone()), // validator is the unbonder
213                *validator_bid.bonding_purse(),
214                staked_amount,
215                None,
216            )?;
217            // Unbond all delegators and zero them out
218            let delegators = read_delegator_bids(self, &public_key)?;
219            for mut delegator in delegators {
220                let unbond_kind = delegator.unbond_kind();
221                detail::create_unbonding_purse(
222                    self,
223                    public_key.clone(),
224                    unbond_kind,
225                    *delegator.bonding_purse(),
226                    delegator.staked_amount(),
227                    None,
228                )?;
229                delegator.decrease_stake(delegator.staked_amount(), era_end_timestamp_millis)?;
230
231                let delegator_bid_addr = delegator.bid_addr();
232                debug!("pruning delegator bid {}", delegator_bid_addr);
233                self.prune_bid(delegator_bid_addr)
234            }
235            debug!("pruning validator bid {}", validator_bid_addr);
236            self.prune_bid(validator_bid_addr);
237        } else {
238            // create unbonding purse for the unbonding amount
239            detail::create_unbonding_purse(
240                self,
241                public_key.clone(),
242                UnbondKind::Validator(public_key.clone()), // validator is the unbonder
243                *validator_bid.bonding_purse(),
244                unbonding_amount,
245                None,
246            )?;
247            self.write_bid(validator_bid_key, BidKind::Validator(validator_bid))?;
248        }
249
250        Ok(updated_stake)
251    }
252
253    /// Adds a new delegator to delegators or increases its current stake. If the target validator
254    /// is missing, the function call returns an error and does nothing.
255    ///
256    /// The function transfers motes from the source purse to the delegator's bonding purse.
257    ///
258    /// This entry point returns the number of tokens currently delegated to a given validator.
259    fn delegate(
260        &mut self,
261        delegator_kind: DelegatorKind,
262        validator_public_key: PublicKey,
263        amount: U512,
264        max_delegators_per_validator: u32,
265    ) -> Result<U512, ApiError> {
266        if !self.allow_auction_bids() {
267            // The auction process can be disabled on a given network.
268            return Err(Error::AuctionBidsDisabled.into());
269        }
270
271        let source = match &delegator_kind {
272            DelegatorKind::PublicKey(pk) => {
273                let account_hash = pk.to_account_hash();
274                if !self.is_allowed_session_caller(&account_hash) {
275                    return Err(Error::InvalidContext.into());
276                }
277                self.get_main_purse()?
278            }
279            DelegatorKind::Purse(addr) => {
280                let uref = URef::new(*addr, AccessRights::WRITE);
281                if !self.is_valid_uref(uref) {
282                    return Err(Error::InvalidContext.into());
283                }
284                uref
285            }
286        };
287
288        detail::handle_delegation(
289            self,
290            delegator_kind,
291            validator_public_key,
292            source,
293            amount,
294            max_delegators_per_validator,
295        )
296    }
297
298    /// Unbonds aka reduces stake by specified amount, adding an entry to the unbonding queue
299    ///
300    /// The arguments are the delegator's key, the validator's key, and the amount.
301    ///
302    /// Returns the remaining staked amount (we allow partial unbonding).
303    fn undelegate(
304        &mut self,
305        delegator_kind: DelegatorKind,
306        validator_public_key: PublicKey,
307        amount: U512,
308    ) -> Result<U512, Error> {
309        let redelegate_target = None;
310        process_undelegation(
311            self,
312            delegator_kind,
313            validator_public_key,
314            amount,
315            redelegate_target,
316        )
317    }
318
319    /// Unbonds aka reduces stake by specified amount, adding an entry to the unbonding queue,
320    /// which when processed will attempt to re-delegate the stake to the specified new validator.
321    /// If this is not possible at that future point in time, the unbonded stake will instead
322    /// downgrade to a standard undelegate operation automatically (the unbonded stake is
323    /// returned to the associated purse).
324    ///
325    /// This is a quality of life / convenience method, allowing a delegator to indicate they
326    /// would like some or all of their stake moved away from a validator to a different validator
327    /// with a single transaction, instead of requiring them to send an unbonding transaction
328    /// to unbond from the first validator and then wait a number of eras equal to the unbonding
329    /// delay and then send a second transaction to bond to the second validator.
330    ///
331    /// The arguments are the delegator's key, the existing validator's key, the amount,
332    /// and the new validator's key.
333    ///
334    /// Returns the remaining staked amount (we allow partial unbonding).
335    fn redelegate(
336        &mut self,
337        delegator_kind: DelegatorKind,
338        validator_public_key: PublicKey,
339        amount: U512,
340        new_validator: PublicKey,
341    ) -> Result<U512, Error> {
342        let redelegate_target = Some(new_validator);
343        process_undelegation(
344            self,
345            delegator_kind,
346            validator_public_key,
347            amount,
348            redelegate_target,
349        )
350    }
351
352    /// Adds new reservations for a given validator with specified delegator public keys
353    /// and delegation rates. If during adding reservations configured number of reserved
354    /// delegator slots is exceeded it returns an error.
355    ///
356    /// If given reservation exists already and the delegation rate was changed it's updated.
357    fn add_reservations(&mut self, reservations: Vec<Reservation>) -> Result<(), Error> {
358        if !self.allow_auction_bids() {
359            // The auction process can be disabled on a given network.
360            return Err(Error::AuctionBidsDisabled);
361        }
362
363        for reservation in reservations {
364            if !self
365                .is_allowed_session_caller(&AccountHash::from(reservation.validator_public_key()))
366            {
367                return Err(Error::InvalidContext);
368            }
369
370            detail::handle_add_reservation(self, reservation)?;
371        }
372        Ok(())
373    }
374
375    /// Removes reservations for given delegator public keys. If a reservation for one of the keys
376    /// does not exist it returns an error.
377    fn cancel_reservations(
378        &mut self,
379        validator: PublicKey,
380        delegators: Vec<DelegatorKind>,
381        max_delegators_per_validator: u32,
382    ) -> Result<(), Error> {
383        if !self.is_allowed_session_caller(&AccountHash::from(&validator)) {
384            return Err(Error::InvalidContext);
385        }
386
387        for delegator in delegators {
388            detail::handle_cancel_reservation(
389                self,
390                validator.clone(),
391                delegator.clone(),
392                max_delegators_per_validator,
393            )?;
394        }
395        Ok(())
396    }
397
398    /// Slashes each validator.
399    ///
400    /// This can be only invoked through a system call.
401    fn slash(&mut self, validator_public_keys: Vec<PublicKey>) -> Result<(), Error> {
402        fn slash_unbonds(unbond_eras: Vec<UnbondEra>) -> U512 {
403            let mut burned_amount = U512::zero();
404            for unbond_era in unbond_eras {
405                burned_amount += *unbond_era.amount();
406            }
407            burned_amount
408        }
409
410        if self.get_caller() != PublicKey::System.to_account_hash() {
411            return Err(Error::InvalidCaller);
412        }
413
414        let mut burned_amount: U512 = U512::zero();
415
416        for validator_public_key in validator_public_keys {
417            let validator_bid_addr = BidAddr::from(validator_public_key.clone());
418            // Burn stake, deactivate
419            if let Some(BidKind::Validator(validator_bid)) =
420                self.read_bid(&validator_bid_addr.into())?
421            {
422                burned_amount += validator_bid.staked_amount();
423                self.prune_bid(validator_bid_addr);
424
425                // Also slash delegator stakes when deactivating validator bid.
426                let delegator_keys = {
427                    let mut ret =
428                        self.get_keys_by_prefix(&validator_bid_addr.delegated_account_prefix()?)?;
429                    ret.extend(
430                        self.get_keys_by_prefix(&validator_bid_addr.delegated_purse_prefix()?)?,
431                    );
432                    ret
433                };
434
435                for delegator_key in delegator_keys {
436                    if let Some(BidKind::Delegator(delegator_bid)) =
437                        self.read_bid(&delegator_key)?
438                    {
439                        burned_amount += delegator_bid.staked_amount();
440                        let delegator_bid_addr = delegator_bid.bid_addr();
441                        self.prune_bid(delegator_bid_addr);
442
443                        // Also slash delegator unbonds.
444                        let delegator_unbond_addr = match delegator_bid.delegator_kind() {
445                            DelegatorKind::PublicKey(pk) => BidAddr::UnbondAccount {
446                                validator: validator_public_key.to_account_hash(),
447                                unbonder: pk.to_account_hash(),
448                            },
449                            DelegatorKind::Purse(addr) => BidAddr::UnbondPurse {
450                                validator: validator_public_key.to_account_hash(),
451                                unbonder: *addr,
452                            },
453                        };
454
455                        match self.read_unbond(delegator_unbond_addr)? {
456                            Some(unbond) => {
457                                let burned = slash_unbonds(unbond.take_eras());
458
459                                burned_amount += burned;
460                                self.write_unbond(delegator_unbond_addr, None)?;
461                            }
462                            None => {
463                                continue;
464                            }
465                        }
466                    }
467                }
468            }
469
470            // get rid of any staked token in the unbonding queue
471            let validator_unbond_addr = BidAddr::UnbondAccount {
472                validator: validator_public_key.to_account_hash(),
473                unbonder: validator_public_key.to_account_hash(),
474            };
475            match self.read_unbond(validator_unbond_addr)? {
476                Some(unbond) => {
477                    let burned = slash_unbonds(unbond.take_eras());
478                    burned_amount += burned;
479                    self.write_unbond(validator_unbond_addr, None)?;
480                }
481                None => {
482                    continue;
483                }
484            }
485        }
486
487        self.reduce_total_supply(burned_amount)?;
488
489        Ok(())
490    }
491
492    /// Takes active_bids and delegators to construct a list of validators' total bids (their own
493    /// added to their delegators') ordered by size from largest to smallest, then takes the top N
494    /// (number of auction slots) bidders and replaces era_validators with these.
495    ///
496    /// Accessed by: node
497    fn run_auction(
498        &mut self,
499        era_end_timestamp_millis: u64,
500        evicted_validators: Vec<PublicKey>,
501        max_delegators_per_validator: u32,
502        include_credits: bool,
503        credit_cap: Ratio<U512>,
504        minimum_bid_amount: u64,
505    ) -> Result<(), ApiError> {
506        debug!("run_auction called");
507
508        if self.get_caller() != PublicKey::System.to_account_hash() {
509            return Err(Error::InvalidCaller.into());
510        }
511
512        let vesting_schedule_period_millis = self.vesting_schedule_period_millis();
513        let validator_slots = detail::get_validator_slots(self)?;
514        let auction_delay = detail::get_auction_delay(self)?;
515        // We have to store auction_delay future eras, one current era and one past era (for
516        // rewards calculations).
517        let snapshot_size = auction_delay as usize + 2;
518        let mut era_id: EraId = detail::get_era_id(self)?;
519
520        // Process unbond requests
521        debug!("processing unbond requests");
522        detail::process_unbond_requests(self, max_delegators_per_validator)?;
523        debug!("processing unbond request successful");
524
525        let mut validator_bids_detail = detail::get_validator_bids(self, era_id)?;
526
527        // Process bids
528        let mut bids_modified = false;
529        for (validator_public_key, validator_bid) in
530            validator_bids_detail.validator_bids_mut().iter_mut()
531        {
532            if process_with_vesting_schedule(
533                self,
534                validator_bid,
535                era_end_timestamp_millis,
536                self.vesting_schedule_period_millis(),
537            )? {
538                bids_modified = true;
539            }
540
541            if evicted_validators.contains(validator_public_key) {
542                validator_bid.deactivate();
543                bids_modified = true;
544            }
545        }
546
547        let winners = validator_bids_detail.pick_winners(
548            era_id,
549            validator_slots,
550            minimum_bid_amount,
551            include_credits,
552            credit_cap,
553            era_end_timestamp_millis,
554            vesting_schedule_period_millis,
555        )?;
556
557        let (validator_bids, validator_credits, delegator_bids, reservations) =
558            validator_bids_detail.destructure();
559
560        // call prune BEFORE incrementing the era
561        detail::prune_validator_credits(self, era_id, &validator_credits);
562
563        // Increment era
564        era_id = era_id.checked_add(1).ok_or(Error::ArithmeticOverflow)?;
565
566        let delayed_era = era_id
567            .checked_add(auction_delay)
568            .ok_or(Error::ArithmeticOverflow)?;
569
570        // Update seigniorage recipients for current era
571        {
572            let mut snapshot = detail::get_seigniorage_recipients_snapshot(self)?;
573            let recipients =
574                seigniorage_recipients(&winners, &validator_bids, &delegator_bids, &reservations)?;
575            let previous_recipients = snapshot.insert(delayed_era, recipients);
576            assert!(previous_recipients.is_none());
577
578            let snapshot = snapshot.into_iter().rev().take(snapshot_size).collect();
579            detail::set_seigniorage_recipients_snapshot(self, snapshot)?;
580        }
581
582        detail::set_era_id(self, era_id)?;
583        detail::set_era_end_timestamp_millis(self, era_end_timestamp_millis)?;
584
585        if bids_modified {
586            detail::set_validator_bids(self, validator_bids)?;
587        }
588
589        debug!("run_auction successful");
590
591        Ok(())
592    }
593
594    /// Mint and distribute seigniorage rewards to validators and their delegators,
595    /// according to `reward_factors` returned by the consensus component.
596    // TODO: rework EraInfo and other related structs, methods, etc. to report correct era-end
597    // totals of per-block rewards
598    fn distribute(&mut self, rewards: BTreeMap<PublicKey, Vec<U512>>) -> Result<(), Error> {
599        if self.get_caller() != PublicKey::System.to_account_hash() {
600            error!("invalid caller to auction distribute");
601            return Err(Error::InvalidCaller);
602        }
603
604        debug!("reading seigniorage recipients snapshot");
605        let seigniorage_recipients_snapshot = detail::get_seigniorage_recipients_snapshot(self)?;
606        let current_era_id = detail::get_era_id(self)?;
607
608        let mut era_info = EraInfo::new();
609        let seigniorage_allocations = era_info.seigniorage_allocations_mut();
610
611        debug!(rewards_set_size = rewards.len(), "processing rewards");
612        for item in rewards
613            .into_iter()
614            .filter(|(key, _amounts)| key != &PublicKey::System)
615            .map(|(proposer, amounts)| {
616                rewards_per_validator(
617                    &proposer,
618                    current_era_id,
619                    &amounts,
620                    &SeigniorageRecipientsSnapshot::V2(seigniorage_recipients_snapshot.clone()),
621                )
622                .map(|infos| infos.into_iter().map(move |info| (proposer.clone(), info)))
623            })
624            .flatten_ok()
625        {
626            let (validator_public_key, reward_info) = item?;
627
628            let validator_bid_addr = BidAddr::Validator(validator_public_key.to_account_hash());
629            let mut maybe_bridged_validator_addrs: Option<Vec<BidAddr>> = None;
630            let validator_reward_amount = reward_info.validator_reward();
631            let (validator_bonding_purse, min_del, max_del) =
632                match detail::get_distribution_target(self, validator_bid_addr) {
633                    Ok(target) => match target {
634                        DistributeTarget::Validator(mut validator_bid) => {
635                            debug!(?validator_public_key, "validator payout starting ");
636                            let validator_bonding_purse = *validator_bid.bonding_purse();
637                            validator_bid.increase_stake(validator_reward_amount)?;
638
639                            self.write_bid(
640                                validator_bid_addr.into(),
641                                BidKind::Validator(validator_bid.clone()),
642                            )?;
643                            (
644                                validator_bonding_purse,
645                                validator_bid.minimum_delegation_amount().into(),
646                                validator_bid.maximum_delegation_amount().into(),
647                            )
648                        }
649                        DistributeTarget::BridgedValidator {
650                            requested_validator_bid_addr: _requested_validator_bid_addr,
651                            current_validator_bid_addr,
652                            bridged_validator_addrs,
653                            mut validator_bid,
654                        } => {
655                            debug!(?validator_public_key, "bridged validator payout starting ");
656                            maybe_bridged_validator_addrs = Some(bridged_validator_addrs); // <-- important
657                            let validator_bonding_purse = *validator_bid.bonding_purse();
658                            validator_bid.increase_stake(validator_reward_amount)?;
659
660                            self.write_bid(
661                                current_validator_bid_addr.into(),
662                                BidKind::Validator(validator_bid.clone()),
663                            )?;
664                            (
665                                validator_bonding_purse,
666                                validator_bid.minimum_delegation_amount().into(),
667                                validator_bid.maximum_delegation_amount().into(),
668                            )
669                        }
670                        DistributeTarget::Unbond(unbond) => match unbond.target_unbond_era() {
671                            Some(mut unbond_era) => {
672                                let account_hash = validator_public_key.to_account_hash();
673                                let unbond_addr = BidAddr::UnbondAccount {
674                                    validator: account_hash,
675                                    unbonder: account_hash,
676                                };
677                                let validator_bonding_purse = *unbond_era.bonding_purse();
678                                let new_amount =
679                                    unbond_era.amount().saturating_add(validator_reward_amount);
680                                unbond_era.with_amount(new_amount);
681                                self.write_unbond(unbond_addr, Some(*unbond.clone()))?;
682                                (validator_bonding_purse, U512::MAX, U512::MAX)
683                            }
684                            None => {
685                                warn!(
686                                    ?validator_public_key,
687                                    "neither validator bid or unbond found"
688                                );
689                                continue;
690                            }
691                        },
692                        DistributeTarget::Delegator(_) => {
693                            return Err(Error::UnexpectedBidVariant);
694                        }
695                    },
696                    Err(Error::BridgeRecordChainTooLong) => {
697                        warn!(?validator_public_key, "bridge record chain too long");
698                        continue;
699                    }
700                    Err(err) => return Err(err),
701                };
702
703            self.mint_into_existing_purse(validator_reward_amount, validator_bonding_purse)?;
704            seigniorage_allocations.push(SeigniorageAllocation::validator(
705                validator_public_key.clone(),
706                validator_reward_amount,
707            ));
708            debug!(?validator_public_key, "validator payout finished");
709
710            debug!(?validator_public_key, "delegator payouts for validator");
711            let mut undelegates = vec![];
712            let mut prunes = vec![];
713            for (delegator_kind, delegator_reward) in reward_info.take_delegator_rewards() {
714                let mut delegator_bid_addrs = Vec::with_capacity(2);
715                if let Some(bridged_validator_addrs) = &maybe_bridged_validator_addrs {
716                    for bridged_addr in bridged_validator_addrs {
717                        delegator_bid_addrs.push(BidAddr::new_delegator_kind_relaxed(
718                            bridged_addr.validator_account_hash(),
719                            &delegator_kind,
720                        ))
721                    }
722                }
723                delegator_bid_addrs.push(BidAddr::new_delegator_kind_relaxed(
724                    validator_bid_addr.validator_account_hash(),
725                    &delegator_kind,
726                ));
727                let mut maybe_delegator_bonding_purse: Option<URef> = None;
728                for delegator_bid_addr in delegator_bid_addrs {
729                    if delegator_reward.is_zero() {
730                        maybe_delegator_bonding_purse = None;
731                        break; // if there is no reward to give, no need to continue looking
732                    } else {
733                        let delegator_bid_key = delegator_bid_addr.into();
734                        match detail::get_distribution_target(self, delegator_bid_addr) {
735                            Ok(target) => match target {
736                                DistributeTarget::Delegator(mut delegator_bid) => {
737                                    let delegator_bonding_purse = *delegator_bid.bonding_purse();
738                                    let increased_stake =
739                                        delegator_bid.increase_stake(delegator_reward)?;
740                                    if increased_stake < min_del {
741                                        // update the bid initially, but register for unbond and
742                                        // prune
743                                        undelegates.push((
744                                            delegator_kind.clone(),
745                                            validator_public_key.clone(),
746                                            increased_stake,
747                                        ));
748                                        prunes.push(delegator_bid_addr);
749                                    } else if increased_stake > max_del {
750                                        // update the bid initially, but register overage for unbond
751                                        let unbond_amount = increased_stake.saturating_sub(max_del);
752                                        if !unbond_amount.is_zero() {
753                                            undelegates.push((
754                                                delegator_kind.clone(),
755                                                validator_public_key.clone(),
756                                                unbond_amount,
757                                            ));
758                                        }
759                                    }
760                                    self.write_bid(
761                                        delegator_bid_key,
762                                        BidKind::Delegator(delegator_bid),
763                                    )?;
764                                    maybe_delegator_bonding_purse = Some(delegator_bonding_purse);
765                                    break;
766                                }
767                                DistributeTarget::Unbond(mut unbond) => {
768                                    match unbond.target_unbond_era_mut() {
769                                        Some(unbond_era) => {
770                                            let unbond_addr = BidAddr::new_delegator_unbond_relaxed(
771                                                delegator_bid_addr.validator_account_hash(),
772                                                &delegator_kind,
773                                            );
774                                            let delegator_bonding_purse =
775                                                *unbond_era.bonding_purse();
776                                            let new_amount = unbond_era
777                                                .amount()
778                                                .saturating_add(delegator_reward);
779
780                                            unbond_era.with_amount(new_amount);
781                                            self.write_unbond(unbond_addr, Some(*unbond.clone()))?;
782                                            maybe_delegator_bonding_purse =
783                                                Some(delegator_bonding_purse);
784                                            break;
785                                        }
786                                        None => {
787                                            debug!(
788                                                ?delegator_bid_key,
789                                                "neither delegator bid or unbond found"
790                                            );
791                                            // keep looking
792                                        }
793                                    }
794                                }
795                                DistributeTarget::Validator(_)
796                                | DistributeTarget::BridgedValidator { .. } => {
797                                    return Err(Error::UnexpectedBidVariant)
798                                }
799                            },
800                            Err(Error::DelegatorNotFound) => {
801                                debug!(
802                                    ?validator_public_key,
803                                    ?delegator_bid_addr,
804                                    "delegator bid not found"
805                                );
806                                // keep looking
807                            }
808                            Err(err) => return Err(err),
809                        }
810                    }
811                }
812
813                // we include 0 allocations for explicitness
814                let allocation = SeigniorageAllocation::delegator_kind(
815                    delegator_kind,
816                    validator_public_key.clone(),
817                    delegator_reward,
818                );
819                seigniorage_allocations.push(allocation);
820                if let Some(delegator_bonding_purse) = maybe_delegator_bonding_purse {
821                    self.mint_into_existing_purse(delegator_reward, delegator_bonding_purse)?;
822                }
823            }
824
825            for (kind, pk, unbond_amount) in undelegates {
826                debug!(?kind, ?pk, ?unbond_amount, "unbonding delegator");
827                self.undelegate(kind, pk, unbond_amount)?;
828            }
829
830            for bid_addr in prunes {
831                debug!(?bid_addr, "pruning bid");
832                self.prune_bid(bid_addr);
833            }
834
835            debug!(
836                ?validator_public_key,
837                delegator_set_size = seigniorage_allocations.len(),
838                "delegator payout finished"
839            );
840
841            debug!(
842                ?validator_public_key,
843                "rewards minted into recipient purses"
844            );
845        }
846
847        // record allocations for this era for reporting purposes.
848        self.record_era_info(era_info)?;
849
850        Ok(())
851    }
852
853    /// Reads current era id.
854    fn read_era_id(&mut self) -> Result<EraId, Error> {
855        detail::get_era_id(self)
856    }
857
858    /// Activates a given validator's bid.  To be used when a validator has been marked as inactive
859    /// by consensus (aka "evicted").
860    fn activate_bid(&mut self, validator: PublicKey, minimum_bid: u64) -> Result<(), Error> {
861        let provided_account_hash = AccountHash::from(&validator);
862
863        if !self.is_allowed_session_caller(&provided_account_hash) {
864            return Err(Error::InvalidContext);
865        }
866
867        let key = BidAddr::from(validator).into();
868        if let Some(BidKind::Validator(mut validator_bid)) = self.read_bid(&key)? {
869            if validator_bid.staked_amount() >= minimum_bid.into() {
870                validator_bid.activate();
871                self.write_bid(key, BidKind::Validator(validator_bid))?;
872                Ok(())
873            } else {
874                Err(Error::BondTooSmall)
875            }
876        } else {
877            Err(Error::ValidatorNotFound)
878        }
879    }
880
881    /// Updates a `ValidatorBid` and all related delegator bids to use a new public key.
882    ///
883    /// This in effect "transfers" a validator bid along with its stake and all delegators
884    /// from one public key to another.
885    /// This method can only be called by the account associated with the current `ValidatorBid`.
886    ///
887    /// The arguments are the existing bid's 'validator_public_key' and the new public key.
888    fn change_bid_public_key(
889        &mut self,
890        public_key: PublicKey,
891        new_public_key: PublicKey,
892    ) -> Result<(), Error> {
893        let validator_account_hash = AccountHash::from(&public_key);
894
895        // check that the caller is the current bid's owner
896        if !self.is_allowed_session_caller(&validator_account_hash) {
897            return Err(Error::InvalidContext);
898        }
899
900        // verify that a bid for given public key exists
901        let validator_bid_addr = BidAddr::from(public_key.clone());
902        let mut validator_bid = read_validator_bid(self, &validator_bid_addr.into())?;
903
904        // verify that a bid for the new key does not exist yet
905        let new_validator_bid_addr = BidAddr::from(new_public_key.clone());
906        if self.read_bid(&new_validator_bid_addr.into())?.is_some() {
907            return Err(Error::ValidatorBidExistsAlready);
908        }
909
910        debug!("changing validator bid {validator_bid_addr} public key from {public_key} to {new_public_key}");
911
912        // store new validator bid
913        validator_bid.with_validator_public_key(new_public_key.clone());
914        self.write_bid(
915            new_validator_bid_addr.into(),
916            BidKind::Validator(validator_bid),
917        )?;
918
919        // store bridge record in place of old validator bid
920        let bridge = Bridge::new(
921            public_key.clone(),
922            new_public_key.clone(),
923            self.read_era_id()?,
924        );
925        // write a bridge record under the old account hash, allowing forward pathing
926        // i.e. given an older account hash find the replacement account hash
927        self.write_bid(
928            validator_bid_addr.into(),
929            BidKind::Bridge(Box::new(bridge.clone())),
930        )?;
931        // write a bridge record under the new account hash, allowing reverse pathing
932        // i.e. given a newer account hash find the previous account hash
933        let rev_addr = BidAddr::new_validator_rev_addr_from_public_key(new_public_key.clone());
934        self.write_bid(rev_addr.into(), BidKind::Bridge(Box::new(bridge)))?;
935
936        debug!("transferring delegator bids from validator bid {validator_bid_addr} to {new_validator_bid_addr}");
937        let delegators = read_delegator_bids(self, &public_key)?;
938        for mut delegator in delegators {
939            let delegator_bid_addr =
940                BidAddr::new_delegator_kind(&public_key, delegator.delegator_kind());
941
942            delegator.with_validator_public_key(new_public_key.clone());
943            let new_delegator_bid_addr =
944                BidAddr::new_delegator_kind(&new_public_key, delegator.delegator_kind());
945
946            self.write_bid(
947                new_delegator_bid_addr.into(),
948                BidKind::Delegator(Box::from(delegator)),
949            )?;
950
951            debug!("pruning delegator bid {delegator_bid_addr}");
952            self.prune_bid(delegator_bid_addr);
953        }
954
955        Ok(())
956    }
957
958    /// Writes a validator credit record.
959    fn write_validator_credit(
960        &mut self,
961        validator: PublicKey,
962        era_id: EraId,
963        amount: U512,
964    ) -> Result<Option<BidAddr>, Error> {
965        // only the system may use this method
966        if self.get_caller() != PublicKey::System.to_account_hash() {
967            error!("invalid caller to auction validator_credit");
968            return Err(Error::InvalidCaller);
969        }
970
971        // is imputed public key associated with a validator bid record?
972        let bid_addr = BidAddr::new_from_public_keys(&validator, None);
973        let key = Key::BidAddr(bid_addr);
974        let _ = match self.read_bid(&key)? {
975            Some(bid_kind) => bid_kind,
976            None => {
977                warn!(
978                    ?key,
979                    ?era_id,
980                    ?amount,
981                    "attempt to add a validator credit to a non-existent validator"
982                );
983                return Ok(None);
984            }
985        };
986
987        // if amount is zero, noop
988        if amount.is_zero() {
989            return Ok(None);
990        }
991
992        // write credit record
993        let credit_addr = BidAddr::new_credit(&validator, era_id);
994        let credit_key = Key::BidAddr(credit_addr);
995        let credit_bid = match self.read_bid(&credit_key)? {
996            Some(BidKind::Credit(mut existing_credit)) => {
997                existing_credit.increase(amount);
998                existing_credit
999            }
1000            Some(_) => return Err(Error::UnexpectedBidVariant),
1001            None => Box::new(ValidatorCredit::new(validator, era_id, amount)),
1002        };
1003
1004        self.write_bid(credit_key, BidKind::Credit(credit_bid))
1005            .map(|_| Some(credit_addr))
1006    }
1007}