diff --git a/src/proto_alpha/lib_protocol/TEZOS_PROTOCOL b/src/proto_alpha/lib_protocol/TEZOS_PROTOCOL index f3fe0c4a2c94ad2de675ddb0e52f9d4e5589fa79..d18221743251fb26dbc6e862ae16dcb7140ebba4 100644 --- a/src/proto_alpha/lib_protocol/TEZOS_PROTOCOL +++ b/src/proto_alpha/lib_protocol/TEZOS_PROTOCOL @@ -135,6 +135,7 @@ "Contract_delegate_storage", "Stake_storage", "Unstaked_frozen_deposits_storage", + "Unstake_requests_storage", "Frozen_deposits_storage", "Contract_storage", "Token", diff --git a/src/proto_alpha/lib_protocol/dune b/src/proto_alpha/lib_protocol/dune index 5685db9d6b2a0f7eeee1b7374d909488ec5cba4b..38a190b09180da6abceda9bee0321d244730dec3 100644 --- a/src/proto_alpha/lib_protocol/dune +++ b/src/proto_alpha/lib_protocol/dune @@ -153,6 +153,7 @@ Contract_delegate_storage Stake_storage Unstaked_frozen_deposits_storage + Unstake_requests_storage Frozen_deposits_storage Contract_storage Token @@ -414,6 +415,7 @@ contract_delegate_storage.ml contract_delegate_storage.mli stake_storage.ml stake_storage.mli unstaked_frozen_deposits_storage.ml unstaked_frozen_deposits_storage.mli + unstake_requests_storage.ml unstake_requests_storage.mli frozen_deposits_storage.ml frozen_deposits_storage.mli contract_storage.ml contract_storage.mli token.ml token.mli @@ -677,6 +679,7 @@ contract_delegate_storage.ml contract_delegate_storage.mli stake_storage.ml stake_storage.mli unstaked_frozen_deposits_storage.ml unstaked_frozen_deposits_storage.mli + unstake_requests_storage.ml unstake_requests_storage.mli frozen_deposits_storage.ml frozen_deposits_storage.mli contract_storage.ml contract_storage.mli token.ml token.mli @@ -924,6 +927,7 @@ contract_delegate_storage.ml contract_delegate_storage.mli stake_storage.ml stake_storage.mli unstaked_frozen_deposits_storage.ml unstaked_frozen_deposits_storage.mli + unstake_requests_storage.ml unstake_requests_storage.mli frozen_deposits_storage.ml frozen_deposits_storage.mli contract_storage.ml contract_storage.mli token.ml token.mli diff --git a/src/proto_alpha/lib_protocol/storage.ml b/src/proto_alpha/lib_protocol/storage.ml index baad42e08cdb94d8697f74c3609113a3926d0981..9e8e5acdbd831d5e27e54e19bf5000dcd9d459a6 100644 --- a/src/proto_alpha/lib_protocol/storage.ml +++ b/src/proto_alpha/lib_protocol/storage.ml @@ -181,6 +181,31 @@ module Slashed_deposits_history = struct loop [] history end +module Unstake_request = struct + type request = Cycle_repr.t * Tez_repr.t + + type requests = request list + + type t = {delegate : Signature.Public_key_hash.t; requests : requests} + + let request_encoding = + let open Data_encoding in + obj2 + (req "cycle" Cycle_repr.encoding) + (req "requested_amount" Tez_repr.encoding) + + let requests_encoding = Data_encoding.list request_encoding + + let encoding = + let open Data_encoding in + conv + (fun {delegate; requests} -> (delegate, requests)) + (fun (delegate, requests) -> {delegate; requests}) + (obj2 + (req "delegate" Contract_repr.implicit_encoding) + (req "requests" requests_encoding)) +end + module Contract = struct module Raw_context = Make_subcontext (Registered) (Raw_context) @@ -403,6 +428,14 @@ module Contract = struct (Make_index (Cycle_repr.Index)) (Deposits_repr) + module Unstake_requests = + Indexed_context.Make_map + (Registered) + (struct + let name = ["unstake_requests"] + end) + (Unstake_request) + module Frozen_deposits_limit = Indexed_context.Make_map (Registered) diff --git a/src/proto_alpha/lib_protocol/storage.mli b/src/proto_alpha/lib_protocol/storage.mli index cfb97c0c43a34ee2fce590032b16afa9991e8f74..79e21499d427b545ed216e60ce0d3609f5b0ab33 100644 --- a/src/proto_alpha/lib_protocol/storage.mli +++ b/src/proto_alpha/lib_protocol/storage.mli @@ -66,6 +66,14 @@ module Slashed_deposits_history : sig val add : Cycle_repr.t -> slashed_percentage -> t -> t end +module Unstake_request : sig + type request = Cycle_repr.t * Tez_repr.t + + type requests = request list + + type t = {delegate : Signature.Public_key_hash.t; requests : requests} +end + module Contract : sig (** Storage from this submodule must only be accessed through the module `Contract`. *) @@ -182,6 +190,13 @@ module Contract : sig and type value = Deposits_repr.t and type t := Raw_context.t * Contract_repr.t + (** The contract's unstake requests that haven't been finalized yet. *) + module Unstake_requests : + Indexed_data_storage + with type key = Contract_repr.t + and type value = Unstake_request.t + and type t := Raw_context.t + (** If there is a value, the frozen balance for the contract won't exceed it (starting in preserved_cycles + 1). *) module Frozen_deposits_limit : diff --git a/src/proto_alpha/lib_protocol/unstake_requests_storage.ml b/src/proto_alpha/lib_protocol/unstake_requests_storage.ml new file mode 100644 index 0000000000000000000000000000000000000000..7176f00912d066882ee5891160cd54929b1ff8f2 --- /dev/null +++ b/src/proto_alpha/lib_protocol/unstake_requests_storage.ml @@ -0,0 +1,97 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2023 Nomadic Labs *) +(* *) +(* 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. *) +(* *) +(*****************************************************************************) + +type prepared_finalize_unstake = { + finalizable : (Signature.Public_key_hash.t * Cycle_repr.t * Tez_repr.t) list; + unfinalizable : Signature.Public_key_hash.t * (Cycle_repr.t * Tez_repr.t) list; +} + +let z100 = Z.of_int 100 + +let apply_slashes ~preserved_plus_slashing slashing_history ~from_cycle amount = + let first_cycle_to_apply_slash = from_cycle in + let last_cycle_to_apply_slash = + Cycle_repr.add from_cycle (preserved_plus_slashing - 1) + in + (* TODO https://gitlab.com/tezos/tezos/-/issues/5768 + All slashings are applied multiplicatively so the order should have + no impact. But it does a little because of rounding down. Let's make sure + slashings are always applied in the same order, from oldest to newest. *) + let amount = Z.of_int64 (Tez_repr.to_mutez amount) in + let amount = + List.fold_left + (fun amount (slashing_cycle, slashing_percentage) -> + if + Cycle_repr.( + slashing_cycle >= first_cycle_to_apply_slash + && slashing_cycle <= last_cycle_to_apply_slash) + then Z.div (Z.mul amount (Z.of_int (100 - slashing_percentage))) z100 + else amount) + amount + slashing_history + in + Tez_repr.of_mutez_exn (Z.to_int64 amount) + +let prepare_finalize_unstake ctxt contract = + let open Lwt_result_syntax in + let preserved_cycles = Constants_storage.preserved_cycles ctxt in + let max_slashing_period = Constants_storage.max_slashing_period ctxt in + let preserved_plus_slashing = preserved_cycles + max_slashing_period in + let current_cycle = (Raw_context.current_level ctxt).cycle in + match Cycle_repr.sub current_cycle preserved_plus_slashing with + | None (* no finalizable cycle *) -> return None + | Some greatest_finalizable_cycle -> ( + let* requests_opt = + Storage.Contract.Unstake_requests.find ctxt contract + in + match requests_opt with + | None | Some {delegate = _; requests = []} -> return None + | Some {delegate; requests} -> + let* slashing_history_opt = + Storage.Contract.Slashed_deposits.find + ctxt + (Contract_repr.Implicit delegate) + in + let slashing_history = + Option.value slashing_history_opt ~default:[] + in + let finalizable, unfinalizable = + List.partition_map + (fun request -> + let request_cycle, request_amount = request in + if Cycle_repr.(request_cycle <= greatest_finalizable_cycle) then + let new_amount = + apply_slashes + ~preserved_plus_slashing + slashing_history + ~from_cycle:request_cycle + request_amount + in + Left (delegate, request_cycle, new_amount) + else Right request) + requests + in + let unfinalizable = (delegate, unfinalizable) in + return_some {finalizable; unfinalizable}) diff --git a/src/proto_alpha/lib_protocol/unstake_requests_storage.mli b/src/proto_alpha/lib_protocol/unstake_requests_storage.mli new file mode 100644 index 0000000000000000000000000000000000000000..8bc35b435a59174be3adae1a3c1f359ba065543d --- /dev/null +++ b/src/proto_alpha/lib_protocol/unstake_requests_storage.mli @@ -0,0 +1,47 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2023 Nomadic Labs *) +(* *) +(* 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. *) +(* *) +(*****************************************************************************) + +(** Simple abstraction from low-level storage to handle unstake requests. + + This module is responsible for maintaining the + {!Storage.Contract.Unstake_requests} table. *) + +type prepared_finalize_unstake = { + finalizable : (Signature.Public_key_hash.t * Cycle_repr.t * Tez_repr.t) list; + unfinalizable : Signature.Public_key_hash.t * (Cycle_repr.t * Tez_repr.t) list; +} + +(** [prepare_finalize_unstake ctxt contract] preprocesses a [finalize_unstake] + for [contract]. It returns a list of transfers [(d, c, a)] to do from + delegate's [d] unstaked frozen deposits for cycle [c] of amount [a] in + the [finalizable_field] as well as the remaining unfinalizable requests + that should be kept in the storage in [unfinalizable]. + + It returns [None] if there are no finalizable unstake requests (regardless + of whether there are unstake requests at all). *) +val prepare_finalize_unstake : + Raw_context.t -> + Contract_repr.t -> + prepared_finalize_unstake option tzresult Lwt.t