diff --git a/.gitlab/ci/pipelines/before_merging.yml b/.gitlab/ci/pipelines/before_merging.yml index 4abd183d61d704f7d99ab6363dd47c2d05d5bec1..b53f868259536404d50b0f94b6d48dec2e337523 100644 --- a/.gitlab/ci/pipelines/before_merging.yml +++ b/.gitlab/ci/pipelines/before_merging.yml @@ -7798,7 +7798,7 @@ tezt-memory-3k: junit: $JUNIT when: always retry: 2 - parallel: 4 + parallel: 6 tezt-memory-4k: image: ${build_deps_image_name}:runtime-e2etest-dependencies--${build_deps_image_version} diff --git a/.gitlab/ci/pipelines/schedule_extended_test.yml b/.gitlab/ci/pipelines/schedule_extended_test.yml index fbe4072c98512863ba99fd93dab8838a724b5245..3fafff3bbee9686a820867ff7680e652975f6c0d 100644 --- a/.gitlab/ci/pipelines/schedule_extended_test.yml +++ b/.gitlab/ci/pipelines/schedule_extended_test.yml @@ -7454,7 +7454,7 @@ tezt-memory-3k: junit: $JUNIT when: always retry: 2 - parallel: 4 + parallel: 6 tezt-memory-4k: image: ${build_deps_image_name}:runtime-e2etest-dependencies--${build_deps_image_version} diff --git a/ci/bin/code_verification.ml b/ci/bin/code_verification.ml index 6d018af40e28aab62119b9e9894d66982dc445b2..27e308d95d59a1e4d22ffb84d607e0f7299930ad 100644 --- a/ci/bin/code_verification.ml +++ b/ci/bin/code_verification.ml @@ -1338,7 +1338,7 @@ let jobs pipeline_type = ~name:"tezt-memory-3k" ~tezt_tests:(tezt_tests ~memory_3k:true []) ~tezt_variant:"-memory_3k" - ~parallel:(Vector 4) + ~parallel:(Vector 6) ~dependencies ~rules () diff --git a/src/proto_alpha/lib_protocol/staking.ml b/src/proto_alpha/lib_protocol/staking.ml index 6b7fce09b3e4b8a9489284d7815de97b280a4b2d..53b30cb6b8fb8fda80dc2afa29b6de6c2535b9f7 100644 --- a/src/proto_alpha/lib_protocol/staking.ml +++ b/src/proto_alpha/lib_protocol/staking.ml @@ -195,9 +195,10 @@ let stake_from_unstake_for_delegate ctxt ~for_next_cycle_use_only_after_slashing match unfinalizable_requests_opt with | None -> return (ctxt, [], amount) | Some Unstake_requests_storage.{delegate = delegate_requests; requests} -> - if Signature.Public_key_hash.(delegate <> delegate_requests) then - (* Possible. If reached, stake should not do anything, - so we also set the amount to stake from the liquid part to zero. *) + if + Signature.Public_key_hash.(delegate <> delegate_requests) + && not (List.is_empty requests) + then (* Should not be possible *) return (ctxt, [], Tez_repr.zero) else let* allowed = diff --git a/src/proto_alpha/lib_protocol/test/helpers/scenario_op.ml b/src/proto_alpha/lib_protocol/test/helpers/scenario_op.ml index 9f3701c77faa24a25e19863ae6dfb0dec47351e4..d12946b258bcf35caa16240d6d9f6cb6879ede32 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/scenario_op.ml +++ b/src/proto_alpha/lib_protocol/test/helpers/scenario_op.ml @@ -170,6 +170,7 @@ let set_delegate src_name delegate_name_opt : (t, t) scenarios = let state = (* if self delegating *) if Option.equal String.equal delegate_name_opt (Some src_name) then + let src = State.find_account src_name state in State.update_map ~f:(fun acc_map -> String.Map.add diff --git a/src/proto_alpha/lib_protocol/test/integration/test_scenario_stake.ml b/src/proto_alpha/lib_protocol/test/integration/test_scenario_stake.ml index 2890eb9a4393712c41bd9b294bfa0a211a2c4ba8..f774be782ae795025ed86e69cc04f1247da735ef 100644 --- a/src/proto_alpha/lib_protocol/test/integration/test_scenario_stake.ml +++ b/src/proto_alpha/lib_protocol/test/integration/test_scenario_stake.ml @@ -301,6 +301,51 @@ let odd_behavior = in loop 20 one_cycle +(* Test changing delegate to self delegation while having staked funds. *) +let change_delegate_to_self = + let init_params = + {limit_of_staking_over_baking = Q.one; edge_of_baking_over_staking = Q.one} + in + init_constants () + --> set S.Adaptive_issuance.autostaking_enable false + --> activate_ai `Force --> begin_test ["delegate"] + --> set_delegate_params "delegate" init_params + --> add_account_with_funds + "staker" + ~funder:"delegate" + (Amount (Tez.of_mutez 2_000_000_000_000L)) + --> set_delegate "staker" (Some "delegate") + --> wait_delegate_parameters_activation --> stake "staker" Half --> next_cycle + --> set_delegate "staker" (Some "staker") + (* Can't stake: "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 + consensus_rights_delay + max_slashing_period)." *) + --> assert_failure (stake "staker" Half) + --> unstake "staker" Max_tez + --> wait_n_cycles_f (unstake_wait -- 1) (* Still can't stake. *) + --> check_balance_field "staker" `Unstaked_finalizable Tez.zero + --> assert_failure (stake "staker" Half) + --> next_cycle + (* The unstake request from changing delegates is now finalizable. *) + --> assert_failure + (check_balance_field "staker" `Unstaked_finalizable Tez.zero) + --> assert_success + (* Can directly stake again, which automatically finalizes, + even though the finalizable unstaked request is about a + previous delegate. *) + (stake "staker" Half + --> check_balance_field "staker" `Unstaked_finalizable Tez.zero) + --> (Tag "finalize" + --> (* Explicitly finalize, so that we can check that the balances + are identical to the beginning. This proves that changing + delegates has indeed unstaked all staked funds. *) + finalize "staker" + --> check_snapshot_balances "init" + --> check_balance_field "staker" `Unstaked_finalizable Tez.zero + |+ Tag "don't finalize" --> Empty) + --> stake "staker" Half --> unstake "staker" Half --> stake "staker" Half + (* Test changing delegates while having staked funds. *) let change_delegate = let init_params = @@ -327,6 +372,7 @@ let change_delegate = finalized yet. Try again in a later cycle (no more than consensus_rights_delay + max_slashing_period)." *) --> assert_failure (stake "staker" Half) + --> unstake "staker" Max_tez --> wait_n_cycles_f (unstake_wait -- 1) (* Still can't stake. *) --> check_balance_field "staker" `Unstaked_finalizable Tez.zero --> assert_failure (stake "staker" Half) @@ -340,14 +386,20 @@ let change_delegate = previous delegate. *) (stake "staker" Half --> check_balance_field "staker" `Unstaked_finalizable Tez.zero) - --> (* Explicitly finalize, so that we can check that the balances - are identical to the beginning. This proves that changing - delegates has indeed unstaked all staked funds. *) - finalize "staker" - --> check_snapshot_balances "init" - --> check_balance_field "staker" `Unstaked_finalizable Tez.zero - --> (* Staking again is also possible. *) stake "staker" Half - --> check_snapshot_balances "after_stake" + --> (Tag "finalize" + --> (* Explicitly finalize, so that we can check that the balances + are identical to the beginning. This proves that changing + delegates has indeed unstaked all staked funds. *) + finalize "staker" + --> check_snapshot_balances "init" + --> check_balance_field "staker" `Unstaked_finalizable Tez.zero + --> (* Staking again is also possible. *) stake "staker" Half + --> check_snapshot_balances "after_stake" + |+ Tag "don't finalize" --> stake "staker" Half) + --> (Tag "finally, unstake" --> unstake "staker" Half + |+ Tag "finally, change delegate one last time" + --> set_delegate "staker" (Some "delegate1") + |+ Tag "finally, unset delegate" --> set_delegate "staker" None) let unset_delegate = let init_params = @@ -534,6 +586,7 @@ let tests = ("Test full balance in finalizable", full_balance_in_finalizable); ("Test stake unstake every cycle", odd_behavior); ("Test change delegate", change_delegate); + ("Test change delegate to self", change_delegate_to_self); ("Test unset delegate", unset_delegate); ("Test forbid costake", forbid_costaking); ("Test stake from unstake", shorter_roundtrip_for_baker); diff --git a/src/proto_alpha/lib_protocol/unstake_requests_storage.ml b/src/proto_alpha/lib_protocol/unstake_requests_storage.ml index d6ca6d6deb5ba2fe8f690189cd3c516997c8c85f..046954b03d02b127ee3895acd3d14f8f20b6bcb8 100644 --- a/src/proto_alpha/lib_protocol/unstake_requests_storage.ml +++ b/src/proto_alpha/lib_protocol/unstake_requests_storage.ml @@ -23,6 +23,27 @@ (* *) (*****************************************************************************) +type error += + | Cannot_unstake_with_unfinalizable_unstake_requests_to_another_delegate + +let () = + register_error_kind + `Permanent + ~id: + "operation.cannot_unstake_with_unfinalizable_unstake_requests_to_another_delegate" + ~title: + "Cannot unstake with unfinalizable unstake requests to another delegate" + ~description: + "Cannot unstake with unfinalizable unstake requests to another delegate" + Data_encoding.unit + (function + | Cannot_unstake_with_unfinalizable_unstake_requests_to_another_delegate + -> + Some () + | _ -> None) + (fun () -> + Cannot_unstake_with_unfinalizable_unstake_requests_to_another_delegate) + type finalizable = (Signature.Public_key_hash.t * Cycle_repr.t * Tez_repr.t) list @@ -165,12 +186,22 @@ let update = Storage.Contract.Unstake_requests.update let add ctxt ~contract ~delegate cycle amount = let open Lwt_result_syntax in let* requests_opt = Storage.Contract.Unstake_requests.find ctxt contract in - let requests = + let*? requests = match requests_opt with - | None -> [] - | Some {delegate = request_delegate; requests} -> - assert (Signature.Public_key_hash.(delegate = request_delegate)) ; - requests + | None -> Ok [] + | Some {delegate = request_delegate; requests} -> ( + match requests with + | [] -> Ok [] + | _ -> + if Signature.Public_key_hash.(delegate <> request_delegate) then + (* This would happen if the staker was allowed to stake towards + a new delegate while having unfinalizable unstake requests, + which is not allowed: it will fail earlier. Also, unstaking + for 0 tez is a noop and does not change the state of the storage, + so it does not allow to reach this error either. *) + Result_syntax.tzfail + Cannot_unstake_with_unfinalizable_unstake_requests_to_another_delegate + else Ok requests) in let*? requests = Storage.Unstake_request.add cycle amount requests in let unstake_request = Storage.Unstake_request.{delegate; requests} in diff --git a/tezt/tests/dal.ml b/tezt/tests/dal.ml index 48d76711efbdb826ea6896201d547f0b67394826..e35378fef5771f03c214bef8a12139a2cec0e9ef 100644 --- a/tezt/tests/dal.ml +++ b/tezt/tests/dal.ml @@ -333,7 +333,7 @@ let with_dal_node ?peers ?attester_profiles ?producer_profiles (* Wrapper scenario functions that should be re-used as much as possible when writing tests. *) -let scenario_with_layer1_node ?regression ?(tags = [team]) +let scenario_with_layer1_node ?regression ?(tags = [team; Tag.memory_3k]) ?additional_bootstrap_accounts ?attestation_lag ?number_of_shards ?number_of_slots ?custom_constants ?commitment_period ?challenge_window ?(dal_enable = true) ?event_sections_levels ?node_arguments diff --git a/tezt/tests/sc_rollup.ml b/tezt/tests/sc_rollup.ml index db67df09aca47f203df3498b538257348589f429..53def98c5c20d2330f8cc622748196afdf058e0d 100644 --- a/tezt/tests/sc_rollup.ml +++ b/tezt/tests/sc_rollup.ml @@ -1514,7 +1514,7 @@ let test_commitment_scenario ?supports ?commitment_period ?challenge_window ?commitment_period ?challenge_window { - tags = ["commitment"] @ extra_tags; + tags = ["commitment"; Tag.memory_3k] @ extra_tags; variant = Some variant; description = "rollup node - correct handling of commitments"; }