diff --git a/src/proto_alpha/lib_protocol/TEZOS_PROTOCOL b/src/proto_alpha/lib_protocol/TEZOS_PROTOCOL index 8637a7aa5e93050a1776e452f6dd6899734df52a..5e6eb2542d29fe006bf8fc84eb90cfb67c511369 100644 --- a/src/proto_alpha/lib_protocol/TEZOS_PROTOCOL +++ b/src/proto_alpha/lib_protocol/TEZOS_PROTOCOL @@ -235,6 +235,7 @@ "Dal_apply", "Zk_rollup_apply", + "Staking", "Baking", "Validate_errors", "Amendment", diff --git a/src/proto_alpha/lib_protocol/alpha_context.ml b/src/proto_alpha/lib_protocol/alpha_context.ml index 581a7f636b55cfc0c8271082193575564caba1a5..49952e0f606751553bbf5805cc0421aeb5b39c39 100644 --- a/src/proto_alpha/lib_protocol/alpha_context.ml +++ b/src/proto_alpha/lib_protocol/alpha_context.ml @@ -615,6 +615,7 @@ end module Token = Token module Cache = Cache_repr +module Unstake_requests = Unstake_requests_storage module Internal_for_tests = struct let to_raw x = x diff --git a/src/proto_alpha/lib_protocol/alpha_context.mli b/src/proto_alpha/lib_protocol/alpha_context.mli index f6cc8d67b7ca45879a5755fd39d14a54accb3157..b3e21f2137714e61272900ae55766c8799461666 100644 --- a/src/proto_alpha/lib_protocol/alpha_context.mli +++ b/src/proto_alpha/lib_protocol/alpha_context.mli @@ -4828,6 +4828,14 @@ module Token : sig (context * Receipt.balance_updates) tzresult Lwt.t end +(** This module re-exports definitions from {!Unstake_requests_storage}. *) +module Unstake_requests : sig + type finalizable = (public_key_hash * Cycle.t * Tez.t) list + + val prepare_finalize_unstake_and_save_remaining_unfinalizable_requests : + context -> Contract.t -> (context * finalizable) tzresult Lwt.t +end + (** This module re-exports definitions from {!Fees_storage}. *) module Fees : sig val record_paid_storage_space : diff --git a/src/proto_alpha/lib_protocol/apply.ml b/src/proto_alpha/lib_protocol/apply.ml index b74ad032ac2e2e9db973e1bc70a1badc0a219c69..e73bc5ddf7bbbe094b19557dcdca77d588d38537 100644 --- a/src/proto_alpha/lib_protocol/apply.ml +++ b/src/proto_alpha/lib_protocol/apply.ml @@ -43,7 +43,9 @@ type error += | Multiple_revelation | Invalid_transfer_to_sc_rollup | Invalid_sender of Destination.t - | Invalid_staking_destination of public_key_hash + | Invalid_self_transaction_destination + | Staking_for_nondelegate_while_costaking_disabled + | Invalid_nonzero_transaction_amount of Tez.t let () = register_error_kind @@ -219,23 +221,53 @@ let () = Data_encoding.(obj1 (req "contract" Destination.encoding)) (function Invalid_sender c -> Some c | _ -> None) (fun c -> Invalid_sender c) ; + let invalid_self_transaction_destination_description = + "A pseudo-transaction destination must equal its sender." + in register_error_kind `Permanent - ~id:"operations.invalid_staking_destination" - ~title:"Invalid destination for a stake operation" - ~description: - "The transaction destination must be a registered delegate. Additionally \ - it must equal the operation source for now." - ~pp:(fun ppf pkh -> + ~id:"operations.invalid_self_transaction_destination" + ~title:"Invalid destination for a pseudo-transaction" + ~description:invalid_self_transaction_destination_description + ~pp:(fun ppf () -> + Format.pp_print_string + ppf + invalid_self_transaction_destination_description) + Data_encoding.unit + (function Invalid_self_transaction_destination -> Some () | _ -> None) + (fun () -> Invalid_self_transaction_destination) ; + let staking_for_nondelegate_while_costaking_disabled_description = + "As long as co-staking is not enabled, staking and unstaking operations \ + are only allowed from delegates." + in + register_error_kind + `Permanent + ~id:"operations.staking_for_nondelegate_while_costaking_disabled" + ~title:"Staking for a non-delegate while co-staking is disabled" + ~description:staking_for_nondelegate_while_costaking_disabled_description + ~pp:(fun ppf () -> + Format.pp_print_string + ppf + staking_for_nondelegate_while_costaking_disabled_description) + Data_encoding.unit + (function + | Staking_for_nondelegate_while_costaking_disabled -> Some () | _ -> None) + (fun () -> Staking_for_nondelegate_while_costaking_disabled) ; + register_error_kind + `Permanent + ~id:"operations.invalid_nonzero_transaction_amount" + ~title:"Invalid non-zero transaction amount" + ~description:"A transaction expected a zero-amount but got non-zero." + ~pp:(fun ppf amount -> Format.fprintf ppf - "Invalid destination (%a) for this stake operation. Only registered \ - delegate are allowed." - Signature.Public_key_hash.pp - pkh) - Data_encoding.(obj1 (req "destination" Contract.implicit_encoding)) - (function Invalid_staking_destination pkh -> Some pkh | _ -> None) - (fun pkh -> Invalid_staking_destination pkh) + "A transaction expected a zero-amount but got %a." + Tez.pp + amount) + Data_encoding.(obj1 (req "amount" Tez.encoding)) + (function + | Invalid_nonzero_transaction_amount amount -> Some amount | _ -> None) + (fun amount -> Invalid_nonzero_transaction_amount amount) open Apply_results open Apply_operation_result @@ -288,16 +320,18 @@ let apply_transaction_to_implicit ~ctxt ~sender ~amount ~pkh ~before_operation = in return (ctxt, result, []) -let apply_stake ~ctxt ~sender ~amount ~delegate ~before_operation = - let contract = Contract.Implicit delegate in +let apply_stake ~ctxt ~sender ~amount ~destination ~before_operation = + let contract = Contract.Implicit destination in (* Staking of zero is forbidden. *) error_when Tez.(amount = zero) (Empty_transaction contract) >>?= fun () -> error_unless - Signature.Public_key_hash.(sender = delegate) - (Invalid_staking_destination delegate) + Signature.Public_key_hash.(sender = destination) + Invalid_self_transaction_destination >>?= fun () -> - Contract.is_delegate ctxt delegate >>=? fun is_delegate -> - error_unless is_delegate (Invalid_staking_destination delegate) >>?= fun () -> + Contract.is_delegate ctxt sender >>=? fun is_delegate -> + error_unless is_delegate Staking_for_nondelegate_while_costaking_disabled + >>?= fun () -> + let delegate = sender in Token.transfer ctxt (`Contract (Contract.Implicit sender)) @@ -322,6 +356,33 @@ let apply_stake ~ctxt ~sender ~amount ~delegate ~before_operation = in return (ctxt, result, []) +let apply_finalize_unstake ~ctxt ~sender ~amount ~destination ~before_operation + = + error_when Tez.(amount <> zero) (Invalid_nonzero_transaction_amount amount) + >>?= fun () -> + error_unless + Signature.Public_key_hash.(sender = destination) + Invalid_self_transaction_destination + >>?= fun () -> + let contract = Contract.Implicit sender in + Contract.allocated ctxt contract >>= fun already_allocated -> + Staking.finalize_unstake ctxt sender >>=? fun (ctxt, balance_updates) -> + let result = + Transaction_to_contract_result + { + storage = None; + lazy_storage_diff = None; + balance_updates; + ticket_receipt = []; + originated_contracts = []; + consumed_gas = Gas.consumed ~since:before_operation ~until:ctxt; + storage_size = Z.zero; + paid_storage_size_diff = Z.zero; + allocated_destination_contract = not already_allocated; + } + in + return (ctxt, result, []) + let transfer_from_any_address ctxt sender destination amount = match sender with | Destination.Contract sender -> @@ -805,10 +866,18 @@ let apply_manager_operation : ~ctxt ~sender:source ~amount - ~delegate:pkh + ~destination:pkh + ~before_operation:ctxt_before_op + | "finalize_unstake", Prim (_, D_Unit, [], _) -> + apply_finalize_unstake + ~ctxt + ~sender:source + ~amount + ~destination:pkh ~before_operation:ctxt_before_op - | ("default" | "stake"), _ -> - (* Only allow [Unit] parameter to implicit accounts' default and stake entrypoints. *) + | ("default" | "stake" | "finalize_unstake"), _ -> + (* Only allow [Unit] parameter to implicit accounts' default, stake, + and finalize_unstake entrypoints. *) tzfail (Script_interpreter.Bad_contract_parameter source_contract) | _ -> tzfail (Script_tc_errors.No_such_entrypoint entrypoint)) >|=? fun (ctxt, res, ops) -> (ctxt, Transaction_result res, ops) diff --git a/src/proto_alpha/lib_protocol/dune b/src/proto_alpha/lib_protocol/dune index e9af55b479c9832b48c54ff0f5fbc915437c405e..0286f1d3a3ee9dac1e0874a5151e7ec89149b925 100644 --- a/src/proto_alpha/lib_protocol/dune +++ b/src/proto_alpha/lib_protocol/dune @@ -240,6 +240,7 @@ Sc_rollup_operations Dal_apply Zk_rollup_apply + Staking Baking Validate_errors Amendment @@ -504,6 +505,7 @@ sc_rollup_operations.ml sc_rollup_operations.mli dal_apply.ml dal_apply.mli zk_rollup_apply.ml zk_rollup_apply.mli + staking.ml staking.mli baking.ml baking.mli validate_errors.ml validate_errors.mli amendment.ml amendment.mli @@ -769,6 +771,7 @@ sc_rollup_operations.ml sc_rollup_operations.mli dal_apply.ml dal_apply.mli zk_rollup_apply.ml zk_rollup_apply.mli + staking.ml staking.mli baking.ml baking.mli validate_errors.ml validate_errors.mli amendment.ml amendment.mli @@ -1018,6 +1021,7 @@ sc_rollup_operations.ml sc_rollup_operations.mli dal_apply.ml dal_apply.mli zk_rollup_apply.ml zk_rollup_apply.mli + staking.ml staking.mli baking.ml baking.mli validate_errors.ml validate_errors.mli amendment.ml amendment.mli diff --git a/src/proto_alpha/lib_protocol/staking.ml b/src/proto_alpha/lib_protocol/staking.ml new file mode 100644 index 0000000000000000000000000000000000000000..b87e9afe5609ec5d42e8428ecc5a1cf8dfe49637 --- /dev/null +++ b/src/proto_alpha/lib_protocol/staking.ml @@ -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. *) +(* *) +(*****************************************************************************) +open Alpha_context + +let finalize_unstake ctxt pkh = + let open Lwt_result_syntax in + let contract = Contract.Implicit pkh in + let* ctxt, finalizable = + Unstake_requests + .prepare_finalize_unstake_and_save_remaining_unfinalizable_requests + ctxt + contract + in + List.fold_left_es + (fun (ctxt, balance_updates) (delegate, cycle, amount) -> + let+ ctxt, new_balance_updates = + Token.transfer + ctxt + (`Unstaked_frozen_deposits (delegate, cycle)) + (`Contract contract) + amount + in + (ctxt, new_balance_updates @ balance_updates)) + (ctxt, []) + finalizable diff --git a/src/proto_alpha/lib_protocol/staking.mli b/src/proto_alpha/lib_protocol/staking.mli new file mode 100644 index 0000000000000000000000000000000000000000..4be8b752f8e214fa1451aeada971b660c005880b --- /dev/null +++ b/src/proto_alpha/lib_protocol/staking.mli @@ -0,0 +1,40 @@ +(*****************************************************************************) +(* *) +(* 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. *) +(* *) +(*****************************************************************************) + +open Alpha_context + +(** [finalize_unstake ctxt pkh] performs the finalization of all unstake + requests from [pkh] that can be finalized. + An unstake request can be finalized if it is old enough, specifically the + requested amount must not be at stake anymore and must not be slashable + anymore, i.e. after [preserved_cycles + max_slashing_period] after the + request. + Amounts are transferred from the [pkh]'s delegate (at request time) unstaked + frozen deposits to [pkh]'s spendable balance, minus slashing the requested + stake undergone in between. *) +val finalize_unstake : + context -> + public_key_hash -> + (context * Receipt.balance_updates) tzresult Lwt.t diff --git a/src/proto_alpha/lib_protocol/unstake_requests_storage.ml b/src/proto_alpha/lib_protocol/unstake_requests_storage.ml index 7176f00912d066882ee5891160cd54929b1ff8f2..710aba989bb24f0902e8d0a4730618da45e80fcd 100644 --- a/src/proto_alpha/lib_protocol/unstake_requests_storage.ml +++ b/src/proto_alpha/lib_protocol/unstake_requests_storage.ml @@ -23,9 +23,12 @@ (* *) (*****************************************************************************) +type finalizable = + (Signature.Public_key_hash.t * Cycle_repr.t * Tez_repr.t) list + 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; + finalizable : finalizable; + unfinalizable : Storage.Unstake_request.t; } let z100 = Z.of_int 100 @@ -77,7 +80,7 @@ let prepare_finalize_unstake ctxt contract = let slashing_history = Option.value slashing_history_opt ~default:[] in - let finalizable, unfinalizable = + let finalizable, unfinalizable_requests = List.partition_map (fun request -> let request_cycle, request_amount = request in @@ -93,5 +96,20 @@ let prepare_finalize_unstake ctxt contract = else Right request) requests in - let unfinalizable = (delegate, unfinalizable) in + let unfinalizable = + Storage.Unstake_request. + {delegate; requests = unfinalizable_requests} + in return_some {finalizable; unfinalizable}) + +let prepare_finalize_unstake_and_save_remaining_unfinalizable_requests ctxt + contract = + let open Lwt_result_syntax in + let* prepared_opt = prepare_finalize_unstake ctxt contract in + match prepared_opt with + | None -> return (ctxt, []) + | Some {finalizable; unfinalizable} -> + let+ ctxt = + Storage.Contract.Unstake_requests.update ctxt contract unfinalizable + in + (ctxt, finalizable) diff --git a/src/proto_alpha/lib_protocol/unstake_requests_storage.mli b/src/proto_alpha/lib_protocol/unstake_requests_storage.mli index 8bc35b435a59174be3adae1a3c1f359ba065543d..f32cbd55e0204826cc65fc01ad59c9f3aae49e1c 100644 --- a/src/proto_alpha/lib_protocol/unstake_requests_storage.mli +++ b/src/proto_alpha/lib_protocol/unstake_requests_storage.mli @@ -28,9 +28,12 @@ This module is responsible for maintaining the {!Storage.Contract.Unstake_requests} table. *) +type finalizable = + (Signature.Public_key_hash.t * Cycle_repr.t * Tez_repr.t) list + 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; + finalizable : finalizable; + unfinalizable : Storage.Unstake_request.t; } (** [prepare_finalize_unstake ctxt contract] preprocesses a [finalize_unstake] @@ -45,3 +48,11 @@ val prepare_finalize_unstake : Raw_context.t -> Contract_repr.t -> prepared_finalize_unstake option tzresult Lwt.t + +(** [prepare_finalize_unstake_and_save_remaining_unfinalizable_requests ctxt contract] + calls [prepare_finalize_unstake], saves the remaining [unfinalizable] + requests and returns the [finalizable] ones. *) +val prepare_finalize_unstake_and_save_remaining_unfinalizable_requests : + Raw_context.t -> + Contract_repr.t -> + (Raw_context.t * finalizable) tzresult Lwt.t