Raw File
stake_storage.ml
(*****************************************************************************)
(*                                                                           *)
(* Open Source License                                                       *)
(* Copyright (c) 2021 Nomadic Labs, <contact@nomadic-labs.com>               *)
(*                                                                           *)
(* 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.                                                 *)
(*                                                                           *)
(*****************************************************************************)

open Misc

module Selected_distribution_for_cycle = struct
  module Cache_client = struct
    type cached_value = (Signature.Public_key_hash.t * Tez_repr.t) list

    let namespace = Cache_repr.create_namespace "stake_distribution"

    let cache_index = 1

    let value_of_identifier ctxt identifier =
      let cycle = Cycle_repr.of_string_exn identifier in
      Storage.Stake.Selected_distribution_for_cycle.get ctxt cycle
  end

  module Cache = (val Cache_repr.register_exn (module Cache_client))

  let identifier_of_cycle cycle = Format.asprintf "%a" Cycle_repr.pp cycle

  let init ctxt cycle stakes =
    let id = identifier_of_cycle cycle in
    Storage.Stake.Selected_distribution_for_cycle.init ctxt cycle stakes
    >>=? fun ctxt ->
    let size = Constants_repr.stake_distribution_size in
    Cache.update ctxt id (Some (stakes, size)) >>?= fun ctxt -> return ctxt

  let get ctxt cycle =
    let id = identifier_of_cycle cycle in
    Cache.find ctxt id >>=? function
    | None -> Storage.Stake.Selected_distribution_for_cycle.get ctxt cycle
    | Some v -> return v

  let remove_existing ctxt cycle =
    let id = identifier_of_cycle cycle in
    (Cache.find ctxt id >>=? function
     | None -> return ctxt
     | Some _ -> Cache.update ctxt id None |> Lwt.return)
    >>=? fun ctxt ->
    Storage.Stake.Selected_distribution_for_cycle.remove_existing ctxt cycle
end

module Delegate_sampler_state = struct
  module Cache_client = struct
    type cached_value =
      (Signature.Public_key.t * Signature.Public_key_hash.t) Sampler.t

    let namespace = Cache_repr.create_namespace "sampler_state"

    let cache_index = 2

    let value_of_identifier ctxt identifier =
      let cycle = Cycle_repr.of_string_exn identifier in
      Storage.Delegate_sampler_state.get ctxt cycle
  end

  module Cache = (val Cache_repr.register_exn (module Cache_client))

  let identifier_of_cycle cycle = Format.asprintf "%a" Cycle_repr.pp cycle

  let init ctxt cycle sampler_state =
    let id = identifier_of_cycle cycle in
    Storage.Delegate_sampler_state.init ctxt cycle sampler_state
    >>=? fun ctxt ->
    let size = Constants_repr.sampler_state_size in
    Cache.update ctxt id (Some (sampler_state, size)) >>?= fun ctxt ->
    return ctxt

  let get ctxt cycle =
    let id = identifier_of_cycle cycle in
    Cache.find ctxt id >>=? function
    | None -> Storage.Delegate_sampler_state.get ctxt cycle
    | Some v -> return v

  let remove_existing ctxt cycle =
    let id = identifier_of_cycle cycle in
    (Cache.find ctxt id >>=? function
     | None -> return ctxt
     | Some _ -> Cache.update ctxt id None |> Lwt.return)
    >>=? fun ctxt -> Storage.Delegate_sampler_state.remove_existing ctxt cycle
end

let get_staking_balance = Storage.Stake.Staking_balance.get

let ensure_stake_inited ctxt delegate =
  Storage.Stake.Staking_balance.mem ctxt delegate >>= function
  | true -> return ctxt
  | false ->
      Frozen_deposits_storage.init ctxt delegate >>=? fun ctxt ->
      Storage.Stake.Staking_balance.init ctxt delegate Tez_repr.zero

let remove_stake ctxt delegate amount =
  ensure_stake_inited ctxt delegate >>=? fun ctxt ->
  let tokens_per_roll = Constants_storage.tokens_per_roll ctxt in
  get_staking_balance ctxt delegate >>=? fun staking_balance_before ->
  Tez_repr.(staking_balance_before -? amount) >>?= fun staking_balance ->
  Storage.Stake.Staking_balance.update ctxt delegate staking_balance
  >>=? fun ctxt ->
  Delegate_activation_storage.is_inactive ctxt delegate >>=? fun inactive ->
  if (not inactive) && Tez_repr.(staking_balance_before >= tokens_per_roll) then
    if Tez_repr.(staking_balance < tokens_per_roll) then
      Storage.Stake.Active_delegate_with_one_roll.remove ctxt delegate
      >>= fun ctxt -> return ctxt
    else return ctxt
  else
    (* The delegate was not in Stake.Active_delegate_with_one_roll,
       either because it was inactive, or because it did not have a
       roll, in which case it still does not have a roll. *)
    return ctxt

let add_stake ctxt delegate amount =
  ensure_stake_inited ctxt delegate >>=? fun ctxt ->
  let tokens_per_roll = Constants_storage.tokens_per_roll ctxt in
  get_staking_balance ctxt delegate >>=? fun staking_balance_before ->
  Tez_repr.(amount +? staking_balance_before) >>?= fun staking_balance ->
  Storage.Stake.Staking_balance.update ctxt delegate staking_balance
  >>=? fun ctxt ->
  if Tez_repr.(staking_balance >= tokens_per_roll) then
    Delegate_activation_storage.is_inactive ctxt delegate >>=? fun inactive ->
    if inactive || Tez_repr.(staking_balance_before >= tokens_per_roll) then
      return ctxt
    else
      Storage.Stake.Active_delegate_with_one_roll.add ctxt delegate ()
      >>= fun ctxt -> return ctxt
  else
    (* The delegate was not in Stake.Active_delegate_with_one_roll,
       because it did not have a roll (as otherwise it would have a
       roll now). *)
    return ctxt

let deactivate_only_call_from_delegate_storage ctxt delegate =
  Storage.Stake.Active_delegate_with_one_roll.remove ctxt delegate

let activate_only_call_from_delegate_storage ctxt delegate =
  ensure_stake_inited ctxt delegate >>=? fun ctxt ->
  get_staking_balance ctxt delegate >>=? fun staking_balance ->
  let tokens_per_roll = Constants_storage.tokens_per_roll ctxt in
  if Tez_repr.(staking_balance >= tokens_per_roll) then
    Storage.Stake.Active_delegate_with_one_roll.add ctxt delegate ()
    >>= fun ctxt -> return ctxt
  else return ctxt

let snapshot ctxt =
  Storage.Stake.Last_snapshot.get ctxt >>=? fun index ->
  Storage.Stake.Last_snapshot.update ctxt (index + 1) >>=? fun ctxt ->
  Storage.Stake.Staking_balance.snapshot ctxt index >>=? fun ctxt ->
  Storage.Stake.Active_delegate_with_one_roll.snapshot ctxt index

let select_distribution_for_cycle ctxt cycle pubkey =
  Storage.Stake.Last_snapshot.get ctxt >>=? fun max_index ->
  Storage.Seed.For_cycle.get ctxt cycle >>=? fun seed ->
  let rd = Seed_repr.initialize_new seed [Bytes.of_string "stake_snapshot"] in
  let seq = Seed_repr.sequence rd 0l in
  let selected_index =
    Seed_repr.take_int32 seq (Int32.of_int max_index) |> fst |> Int32.to_int
  in
  List.fold_left_es
    (fun ctxt index ->
      (if Compare.Int.(index = selected_index) then
       Storage.Stake.Active_delegate_with_one_roll.fold_snapshot
         ctxt
         index
         ~order:`Sorted
         ~init:([], Tez_repr.zero)
         ~f:(fun delegate () (acc, total_stake) ->
           Storage.Stake.Staking_balance.Snapshot.get ctxt (index, delegate)
           >>=? fun staking_balance ->
           let delegate_contract = Contract_repr.implicit_contract delegate in
           Storage.Contract.Frozen_deposits_limit.find ctxt delegate_contract
           >>=? fun frozen_deposits_limit ->
           Storage.Contract.Balance.get ctxt delegate_contract
           >>=? fun balance ->
           Frozen_deposits_storage.get ctxt delegate_contract
           >>=? fun frozen_deposits ->
           Tez_repr.(balance +? frozen_deposits.current_amount)
           >>?= fun total_balance ->
           let frozen_deposits_percentage =
             Constants_storage.frozen_deposits_percentage ctxt
           in
           let stake_to_consider =
             match frozen_deposits_limit with
             | Some frozen_deposits_limit -> (
                 try
                   let max_mutez = Tez_repr.of_mutez_exn Int64.max_int in
                   let frozen_stake_limit =
                     if Tez_repr.(frozen_deposits_limit > div_exn max_mutez 100)
                     then max_mutez
                     else
                       Tez_repr.(
                         div_exn
                           (mul_exn frozen_deposits_limit 100)
                           frozen_deposits_percentage)
                   in
                   Tez_repr.min staking_balance frozen_stake_limit
                 with _ -> staking_balance)
             | None -> staking_balance
           in
           let max_staking_capacity =
             Tez_repr.(
               div_exn (mul_exn total_balance 100) frozen_deposits_percentage)
           in
           let stake_for_cycle =
             Tez_repr.min stake_to_consider max_staking_capacity
           in
           Tez_repr.(total_stake +? stake_for_cycle) >>?= fun total_stake ->
           return ((delegate, stake_for_cycle) :: acc, total_stake))
       >>=? fun (stakes, total_stake) ->
       let stakes =
         List.sort (fun (_, x) (_, y) -> Tez_repr.compare y x) stakes
       in
       Selected_distribution_for_cycle.init ctxt cycle stakes >>=? fun ctxt ->
       Storage.Total_active_stake.add ctxt cycle total_stake >>= fun ctxt ->
       List.fold_left_es
         (fun acc (pkh, stake) ->
           pubkey ctxt pkh >>=? fun pk ->
           return (((pk, pkh), Tez_repr.to_mutez stake) :: acc))
         []
         stakes
       >>=? fun stakes_pk ->
       let state = Sampler.create stakes_pk in
       Delegate_sampler_state.init ctxt cycle state
      else return ctxt)
      >>=? fun ctxt ->
      Storage.Stake.Staking_balance.delete_snapshot ctxt index >>= fun ctxt ->
      Storage.Stake.Active_delegate_with_one_roll.delete_snapshot ctxt index
      >>= fun ctxt -> return ctxt)
    ctxt
    Misc.(0 --> (max_index - 1))
  >>=? fun ctxt -> Storage.Stake.Last_snapshot.update ctxt 0

let select_distribution_for_cycle_do_not_call_except_for_migration =
  select_distribution_for_cycle

let clear_cycle ctxt cycle =
  Storage.Total_active_stake.remove_existing ctxt cycle >>=? fun ctxt ->
  Selected_distribution_for_cycle.remove_existing ctxt cycle >>=? fun ctxt ->
  Delegate_sampler_state.remove_existing ctxt cycle >>=? fun ctxt ->
  Storage.Seed.For_cycle.remove_existing ctxt cycle

let init_first_cycles ctxt pubkey =
  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
      snapshot ctxt >>=? fun ctxt ->
      select_distribution_for_cycle ctxt cycle pubkey)
    ctxt
    (0 --> preserved)
  >>=? fun ctxt ->
  (* Precompute a snapshot for cycle (preserved_cycles + 1) *)
  snapshot ctxt

let fold ctxt ~f ~order init =
  Storage.Stake.Active_delegate_with_one_roll.fold
    ctxt
    ~order
    ~init:(Ok init)
    ~f:(fun delegate () acc ->
      acc >>?= fun acc ->
      get_staking_balance ctxt delegate >>=? fun stake ->
      f (delegate, stake) acc)

let select_new_distribution_at_cycle_end ctxt ~new_cycle =
  let preserved = Constants_storage.preserved_cycles ctxt in
  let for_cycle = Cycle_repr.add new_cycle preserved in
  select_distribution_for_cycle ctxt for_cycle

let clear_at_cycle_end ctxt ~new_cycle =
  let max_slashing_period = Constants_storage.max_slashing_period ctxt in
  match Cycle_repr.sub new_cycle max_slashing_period with
  | None -> return ctxt
  | Some cycle_to_clear -> clear_cycle ctxt cycle_to_clear

let get ctxt delegate =
  Storage.Stake.Active_delegate_with_one_roll.mem ctxt delegate >>= function
  | true -> get_staking_balance ctxt delegate
  | false -> return Tez_repr.zero

let fold_on_active_delegates_with_rolls =
  Storage.Stake.Active_delegate_with_one_roll.fold

let get_selected_distribution = Selected_distribution_for_cycle.get

let find_selected_distribution =
  Storage.Stake.Selected_distribution_for_cycle.find

let prepare_stake_distribution ctxt =
  let level = Level_storage.current ctxt in
  Selected_distribution_for_cycle.get ctxt level.cycle >>=? fun stakes ->
  let stake_distribution =
    List.fold_left
      (fun map (pkh, stake) -> Signature.Public_key_hash.Map.add pkh stake map)
      Signature.Public_key_hash.Map.empty
      stakes
  in
  return
    (Raw_context.init_stake_distribution_for_current_cycle
       ctxt
       stake_distribution)

let get_total_active_stake = Storage.Total_active_stake.get
back to top