diff --git a/src/proto_alpha/lib_protocol/alpha_context.mli b/src/proto_alpha/lib_protocol/alpha_context.mli index 5d03e0a3f2f3e82732c28de989bcf8c2e3b0281a..9e7ca8685b32214be33918ef3726e0931d889ddc 100644 --- a/src/proto_alpha/lib_protocol/alpha_context.mli +++ b/src/proto_alpha/lib_protocol/alpha_context.mli @@ -4839,8 +4839,21 @@ end 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 + type stored_requests = private { + delegate : public_key_hash; + requests : (Cycle.t * Tez.t) list; + } + + type prepared_finalize_unstake = { + finalizable : finalizable; + unfinalizable : stored_requests; + } + + val prepare_finalize_unstake : + context -> Contract.t -> prepared_finalize_unstake option tzresult Lwt.t + + val update : + context -> Contract.t -> stored_requests -> context tzresult Lwt.t end (** This module re-exports definitions from {!Fees_storage}. *) diff --git a/src/proto_alpha/lib_protocol/apply.ml b/src/proto_alpha/lib_protocol/apply.ml index 6fb2d793b29404ebe7f58aaec843b2ff285562f4..a623e7956d67cddc1e821786b33d7feffd0991f1 100644 --- a/src/proto_alpha/lib_protocol/apply.ml +++ b/src/proto_alpha/lib_protocol/apply.ml @@ -321,40 +321,41 @@ let apply_transaction_to_implicit ~ctxt ~sender ~amount ~pkh ~before_operation = return (ctxt, result, []) let apply_stake ~ctxt ~sender ~amount ~destination ~before_operation = + let open Lwt_result_syntax in 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 = destination) - Invalid_self_transaction_destination - >>?= 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)) - (`Frozen_deposits delegate) - amount - >>=? fun (ctxt, balance_updates) -> - (* Since [delegate] is an already existing delegate, it is already allocated. *) - let allocated_destination_contract = false in - 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; - } + let*? () = error_when Tez.(amount = zero) (Empty_transaction contract) in + let*? () = + error_unless + Signature.Public_key_hash.(sender = destination) + Invalid_self_transaction_destination in - return (ctxt, result, []) + let* delegate_opt = Contract.Delegate.find ctxt contract in + match delegate_opt with + | None -> tzfail Staking_for_nondelegate_while_costaking_disabled + | Some delegate when Signature.Public_key_hash.(delegate <> sender) -> + tzfail Staking_for_nondelegate_while_costaking_disabled + | Some delegate -> + let* ctxt, balance_updates = + Staking.stake ctxt ~sender ~delegate amount + in + (* Since [delegate] is an already existing delegate, it is already allocated. *) + let allocated_destination_contract = false in + 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; + } + in + return (ctxt, result, []) let apply_finalize_unstake ~ctxt ~sender ~amount ~destination ~before_operation = diff --git a/src/proto_alpha/lib_protocol/staking.ml b/src/proto_alpha/lib_protocol/staking.ml index 9ed8e30864e905056b33a08c0b8072115a78c375..f87d5ffc59cabf95e8d435224366fc422743bfcb 100644 --- a/src/proto_alpha/lib_protocol/staking.ml +++ b/src/proto_alpha/lib_protocol/staking.ml @@ -22,17 +22,35 @@ (* 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 +type error += + | Cannot_stake_with_unfinalizable_unstake_requests_to_another_delegate + +let () = + let description = + "A contract tries to stake to its delegate while having unstake requests \ + to a previous delegate that cannot be finalized yet. Try again in a later \ + cycle (no more than preserved_cycles + max_slashing_period)." in + register_error_kind + `Permanent + ~id: + "operation.cannot_stake_with_unfinalizable_unstake_requests_to_another_delegate" + ~title: + "Cannot stake with unfinalizable unstake requests to another delegate" + ~description + Data_encoding.unit + (function + | Cannot_stake_with_unfinalizable_unstake_requests_to_another_delegate -> + Some () + | _ -> None) + (fun () -> + Cannot_stake_with_unfinalizable_unstake_requests_to_another_delegate) + +let perform_finalizable_unstake_transfers ctxt contract finalizable = + let open Lwt_result_syntax in List.fold_left_es (fun (ctxt, balance_updates) (delegate, cycle, amount) -> let+ ctxt, new_balance_updates = @@ -46,6 +64,21 @@ let finalize_unstake ctxt pkh = (ctxt, []) finalizable +let finalize_unstake_and_check ~check_unfinalizable ctxt pkh = + let open Lwt_result_syntax in + let contract = Contract.Implicit pkh in + let* prepared_opt = Unstake_requests.prepare_finalize_unstake ctxt contract in + match prepared_opt with + | None -> return (ctxt, []) + | Some {finalizable; unfinalizable} -> + let* () = check_unfinalizable unfinalizable in + let* ctxt = Unstake_requests.update ctxt contract unfinalizable in + perform_finalizable_unstake_transfers ctxt contract finalizable + +let finalize_unstake ctxt pkh = + let check_unfinalizable _unfinalizable = Lwt_result_syntax.return_unit in + finalize_unstake_and_check ~check_unfinalizable ctxt pkh + let punish_delegate ctxt delegate level mistake ~rewarded = let open Lwt_result_syntax in let punish = @@ -74,3 +107,26 @@ let punish_delegate ctxt delegate level mistake ~rewarded = Token.transfer_n ctxt to_reward (`Contract rewarded) in (ctxt, reward_balance_updates @ punish_balance_updates) + +let stake ctxt ~sender ~delegate amount = + let open Lwt_result_syntax in + let check_unfinalizable + Unstake_requests.{delegate = unstake_delegate; requests} = + match requests with + | [] -> return_unit + | _ :: _ -> + fail_when + Signature.Public_key_hash.(delegate <> unstake_delegate) + Cannot_stake_with_unfinalizable_unstake_requests_to_another_delegate + in + let* ctxt, finalize_balance_updates = + finalize_unstake_and_check ~check_unfinalizable ctxt sender + in + let* ctxt, stake_balance_updates = + Token.transfer + ctxt + (`Contract (Contract.Implicit sender)) + (`Frozen_deposits delegate) + amount + in + return (ctxt, stake_balance_updates @ finalize_balance_updates) diff --git a/src/proto_alpha/lib_protocol/staking.mli b/src/proto_alpha/lib_protocol/staking.mli index bf9b986201c0103794dd5226b5122d13405edb9b..5d7af413b97d89de64081bef2e16960b6aaec5e3 100644 --- a/src/proto_alpha/lib_protocol/staking.mli +++ b/src/proto_alpha/lib_protocol/staking.mli @@ -25,6 +25,15 @@ open Alpha_context +(** [stake ctxt ~sender ~delegate amount] add [amount] as [sender]'s stake + to [delegate]. *) +val stake : + context -> + sender:public_key_hash -> + delegate:public_key_hash -> + Tez.t -> + (context * Receipt.balance_updates) tzresult Lwt.t + (** [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 diff --git a/src/proto_alpha/lib_protocol/unstake_requests_storage.ml b/src/proto_alpha/lib_protocol/unstake_requests_storage.ml index 710aba989bb24f0902e8d0a4730618da45e80fcd..012650903e510bad366554cd562297558e12806c 100644 --- a/src/proto_alpha/lib_protocol/unstake_requests_storage.ml +++ b/src/proto_alpha/lib_protocol/unstake_requests_storage.ml @@ -26,9 +26,14 @@ type finalizable = (Signature.Public_key_hash.t * Cycle_repr.t * Tez_repr.t) list +type stored_requests = Storage.Unstake_request.t = { + delegate : Signature.Public_key_hash.t; + requests : (Cycle_repr.t * Tez_repr.t) list; +} + type prepared_finalize_unstake = { finalizable : finalizable; - unfinalizable : Storage.Unstake_request.t; + unfinalizable : stored_requests; } let z100 = Z.of_int 100 @@ -102,14 +107,4 @@ let prepare_finalize_unstake ctxt contract = 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) +let update = Storage.Contract.Unstake_requests.update diff --git a/src/proto_alpha/lib_protocol/unstake_requests_storage.mli b/src/proto_alpha/lib_protocol/unstake_requests_storage.mli index f32cbd55e0204826cc65fc01ad59c9f3aae49e1c..6c6043aa52e332b86cb6fc5445f5af5deb500ad0 100644 --- a/src/proto_alpha/lib_protocol/unstake_requests_storage.mli +++ b/src/proto_alpha/lib_protocol/unstake_requests_storage.mli @@ -31,9 +31,14 @@ type finalizable = (Signature.Public_key_hash.t * Cycle_repr.t * Tez_repr.t) list +type stored_requests = Storage.Unstake_request.t = { + delegate : Signature.Public_key_hash.t; + requests : (Cycle_repr.t * Tez_repr.t) list; +} + type prepared_finalize_unstake = { finalizable : finalizable; - unfinalizable : Storage.Unstake_request.t; + unfinalizable : stored_requests; } (** [prepare_finalize_unstake ctxt contract] preprocesses a [finalize_unstake] @@ -49,10 +54,9 @@ val prepare_finalize_unstake : 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 : +(** [update ctxt contract requests] updates unstake requests for [contract]. *) +val update : Raw_context.t -> Contract_repr.t -> - (Raw_context.t * finalizable) tzresult Lwt.t + stored_requests -> + Raw_context.t tzresult Lwt.t