(*****************************************************************************) (* *) (* Open Source License *) (* Copyright (c) 2018 Dynamic Ledger Solutions, Inc. *) (* Copyright (c) 2021 Nomadic Labs, *) (* Copyright (c) 2022 G.B. Fefe, *) (* *) (* Permission is hereby granted, free of charge, to any person obtaining a *) (* copy of this software and associated documentation files (the "Software"),*) (* to deal in the Software without restriction, including without limitation *) (* the rights to use, copy, modify, merge, publish, distribute, sublicense, *) (* and/or sell copies of the Software, and to permit persons to whom the *) (* Software is furnished to do so, subject to the following conditions: *) (* *) (* The above copyright notice and this permission notice shall be included *) (* in all copies or substantial portions of the Software. *) (* *) (* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*) (* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *) (* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *) (* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*) (* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *) (* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *) (* DEALINGS IN THE SOFTWARE. *) (* *) (*****************************************************************************) let update_activity ctxt last_cycle = let preserved = Constants_storage.preserved_cycles ctxt in match Cycle_repr.sub last_cycle preserved with | None -> return (ctxt, []) | Some _unfrozen_cycle -> Stake_storage.fold_on_active_delegates_with_minimal_stake ctxt ~order:`Sorted ~init:(Ok (ctxt, [])) ~f:(fun delegate () acc -> acc >>?= fun (ctxt, deactivated) -> Delegate_activation_storage.last_cycle_before_deactivation ctxt delegate >>=? fun cycle -> if Cycle_repr.(cycle <= last_cycle) then Stake_storage.set_inactive ctxt delegate >>= fun ctxt -> return (ctxt, delegate :: deactivated) else return (ctxt, deactivated)) >|=? fun (ctxt, deactivated) -> (ctxt, deactivated) (* Return a map from delegates (with active stake at some cycle in the cycle window [from_cycle, to_cycle]) to the maximum of the stake to be deposited for each such cycle (which is just the [frozen_deposits_percentage] of the active stake at that cycle). Also return the delegates that have fallen out of the sliding window. *) let max_frozen_deposits_and_delegates_to_remove ctxt ~from_cycle ~to_cycle = let frozen_deposits_percentage = Constants_storage.frozen_deposits_percentage ctxt in let cycles = Cycle_repr.(from_cycle ---> to_cycle) in (match Cycle_repr.pred from_cycle with | None -> return Signature.Public_key_hash.Set.empty | Some cleared_cycle -> ( Stake_storage.find_selected_distribution ctxt cleared_cycle >|=? fun cleared_cycle_delegates -> match cleared_cycle_delegates with | None -> Signature.Public_key_hash.Set.empty | Some delegates -> List.fold_left (fun set (d, _) -> Signature.Public_key_hash.Set.add d set) Signature.Public_key_hash.Set.empty delegates)) >>=? fun cleared_cycle_delegates -> List.fold_left_es (fun (maxima, delegates_to_remove) (cycle : Cycle_repr.t) -> Stake_storage.get_selected_distribution ctxt cycle >|=? fun active_stakes -> List.fold_left (fun (maxima, delegates_to_remove) (delegate, stake) -> let stake_to_be_deposited = Tez_repr.(div_exn (mul_exn stake frozen_deposits_percentage) 100) in let maxima = Signature.Public_key_hash.Map.update delegate (function | None -> Some stake_to_be_deposited | Some maximum -> Some (Tez_repr.max maximum stake_to_be_deposited)) maxima in let delegates_to_remove = Signature.Public_key_hash.Set.remove delegate delegates_to_remove in (maxima, delegates_to_remove)) (maxima, delegates_to_remove) active_stakes) (Signature.Public_key_hash.Map.empty, cleared_cycle_delegates) cycles let freeze_deposits ?(origin = Receipt_repr.Block_application) ctxt ~new_cycle ~balance_updates = let max_slashable_period = Constants_storage.max_slashing_period ctxt in (* We want to be able to slash for at most [max_slashable_period] *) (match Cycle_repr.(sub new_cycle (max_slashable_period - 1)) with | None -> Storage.Tenderbake.First_level_of_protocol.get ctxt >>=? fun first_level_of_protocol -> let cycle_eras = Raw_context.cycle_eras ctxt in let level = Level_repr.level_from_raw ~cycle_eras first_level_of_protocol in return level.cycle | Some cycle -> return cycle) >>=? fun from_cycle -> let preserved_cycles = Constants_storage.preserved_cycles ctxt in let to_cycle = Cycle_repr.(add new_cycle preserved_cycles) in max_frozen_deposits_and_delegates_to_remove ctxt ~from_cycle ~to_cycle >>=? fun (maxima, delegates_to_remove) -> Signature.Public_key_hash.Map.fold_es (fun delegate maximum_stake_to_be_deposited (ctxt, balance_updates) -> (* Here we make sure to preserve the following invariant : maximum_stake_to_be_deposited <= frozen_deposits + balance See select_distribution_for_cycle *) let delegate_contract = Contract_repr.Implicit delegate in Frozen_deposits_storage.update_initial_amount ctxt delegate_contract maximum_stake_to_be_deposited >>=? fun ctxt -> Frozen_deposits_storage.get ctxt delegate_contract >>=? fun deposits -> let current_amount = deposits.current_amount in if Tez_repr.(current_amount > maximum_stake_to_be_deposited) then Tez_repr.(current_amount -? maximum_stake_to_be_deposited) >>?= fun to_reimburse -> Token.transfer ~origin ctxt (`Frozen_deposits delegate) (`Delegate_balance delegate) to_reimburse >|=? fun (ctxt, bupds) -> (ctxt, bupds @ balance_updates) else if Tez_repr.(current_amount < maximum_stake_to_be_deposited) then Tez_repr.(maximum_stake_to_be_deposited -? current_amount) >>?= fun desired_to_freeze -> Delegate_storage.spendable_balance ctxt delegate >>=? fun balance -> (* In case the delegate hasn't been slashed in this cycle, the following invariant holds: maximum_stake_to_be_deposited <= frozen_deposits + balance See select_distribution_for_cycle If the delegate has been slashed during the cycle, the invariant above doesn't necessarily hold. In this case, we freeze the max we can for the delegate. *) let to_freeze = Tez_repr.(min balance desired_to_freeze) in Token.transfer ~origin ctxt (`Delegate_balance delegate) (`Frozen_deposits delegate) to_freeze >|=? fun (ctxt, bupds) -> (ctxt, bupds @ balance_updates) else return (ctxt, balance_updates)) maxima (ctxt, balance_updates) >>=? fun (ctxt, balance_updates) -> (* Unfreeze deposits (that is, set them to zero) for delegates that were previously in the relevant window (and therefore had some frozen deposits) but are not in the new window; because that means that such a delegate had no active stake in the relevant cycles, and therefore it should have no frozen deposits. *) Signature.Public_key_hash.Set.fold_es (fun delegate (ctxt, balance_updates) -> let delegate_contract = Contract_repr.Implicit delegate in Frozen_deposits_storage.update_initial_amount ctxt delegate_contract Tez_repr.zero >>=? fun ctxt -> Frozen_deposits_storage.get ctxt delegate_contract >>=? fun frozen_deposits -> if Tez_repr.(frozen_deposits.current_amount > zero) then Token.transfer ~origin ctxt (`Frozen_deposits delegate) (`Delegate_balance delegate) frozen_deposits.current_amount >|=? fun (ctxt, bupds) -> (ctxt, bupds @ balance_updates) else return (ctxt, balance_updates)) delegates_to_remove (ctxt, balance_updates) let delegate_has_revealed_nonces delegate unrevelead_nonces_set = not (Signature.Public_key_hash.Set.mem delegate unrevelead_nonces_set) let distribute_endorsing_rewards ctxt last_cycle unrevealed_nonces = let endorsing_reward_per_slot = Constants_storage.endorsing_reward_per_slot ctxt in let unrevealed_nonces_set = List.fold_left (fun set {Storage.Seed.nonce_hash = _; delegate} -> Signature.Public_key_hash.Set.add delegate set) Signature.Public_key_hash.Set.empty unrevealed_nonces in Stake_storage.get_total_active_stake ctxt last_cycle >>=? fun total_active_stake -> Stake_storage.get_selected_distribution ctxt last_cycle >>=? fun delegates -> List.fold_left_es (fun (ctxt, balance_updates) (delegate, active_stake) -> let delegate_contract = Contract_repr.Implicit delegate in Delegate_missed_endorsements_storage .check_and_reset_delegate_participation ctxt delegate >>=? fun (ctxt, sufficient_participation) -> let has_revealed_nonces = delegate_has_revealed_nonces delegate unrevealed_nonces_set in let expected_slots = Delegate_missed_endorsements_storage .expected_slots_for_given_active_stake ctxt ~total_active_stake ~active_stake in let rewards = Tez_repr.mul_exn endorsing_reward_per_slot expected_slots in if sufficient_participation && has_revealed_nonces then (* Sufficient participation: we pay the rewards *) Token.transfer ctxt `Endorsing_rewards (`Contract delegate_contract) rewards >|=? fun (ctxt, payed_rewards_receipts) -> (ctxt, payed_rewards_receipts @ balance_updates) else (* Insufficient participation or unrevealed nonce: no rewards *) Token.transfer ctxt `Endorsing_rewards (`Lost_endorsing_rewards (delegate, not sufficient_participation, not has_revealed_nonces)) rewards >|=? fun (ctxt, payed_rewards_receipts) -> (ctxt, payed_rewards_receipts @ balance_updates)) (ctxt, []) delegates let cycle_end ctxt last_cycle = Seed_storage.cycle_end ctxt last_cycle >>=? fun (ctxt, unrevealed_nonces) -> let new_cycle = Cycle_repr.add last_cycle 1 in Delegate_sampler.select_new_distribution_at_cycle_end ctxt ~new_cycle >>=? fun ctxt -> Delegate_consensus_key.activate ctxt ~new_cycle >>=? fun ctxt -> Delegate_slashed_deposits_storage.clear_outdated_slashed_deposits ctxt ~new_cycle >>= fun ctxt -> distribute_endorsing_rewards ctxt last_cycle unrevealed_nonces >>=? fun (ctxt, balance_updates) -> freeze_deposits ctxt ~new_cycle ~balance_updates >>=? fun (ctxt, balance_updates) -> Stake_storage.clear_at_cycle_end ctxt ~new_cycle >>=? fun ctxt -> Delegate_sampler.clear_outdated_sampling_data ctxt ~new_cycle >>=? fun ctxt -> update_activity ctxt last_cycle >>=? fun (ctxt, deactivated_delagates) -> return (ctxt, balance_updates, deactivated_delagates) let init_first_cycles ctxt ~origin = let preserved = Constants_storage.preserved_cycles ctxt in List.fold_left_es (fun ctxt c -> let cycle = Cycle_repr.of_int32_exn (Int32.of_int c) in Stake_storage.snapshot ctxt >>=? fun ctxt -> (* NB: we need to take several snapshots because select_distribution_for_cycle deletes the snapshots *) Delegate_sampler.select_distribution_for_cycle ctxt cycle) ctxt Misc.(0 --> preserved) >>=? fun ctxt -> let cycle = (Raw_context.current_level ctxt).cycle in freeze_deposits ~origin ~new_cycle:cycle ~balance_updates:[] ctxt