From 804d18f06f1ab256a8c6d2e773a2ff0581addb93 Mon Sep 17 00:00:00 2001 From: Lucas Randazzo Date: Wed, 13 Mar 2024 14:54:28 +0100 Subject: [PATCH 1/5] Proto/tests: add more test cases for shorter_roundtrip_for_baker Test finalization, and container priority --- .../test/integration/test_scenario_stake.ml | 69 +++++++++++++++---- 1 file changed, 57 insertions(+), 12 deletions(-) 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 fb1d2313ab71..39e7afeb47dc 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 @@ -128,26 +128,71 @@ let status_quo_rountrip = --> finalize "staker" --> check_snapshot_balances "init" -(* Test that a baker can stake from unstaked frozen funds. *) +(* Test that a baker can stake from unstaked frozen funds. + The most recent unstakes are prioritized when staking. *) let shorter_roundtrip_for_baker = - let amount = Amount (Tez.of_mutez 333_000_000_000L) in + let unstake_amount = Amount (Tez.of_mutez 222_000_000_000L) in let consensus_rights_delay = - Default_parameters.constants_test.consensus_rights_delay + Default_parameters.constants_mainnet.consensus_rights_delay + (* mainnet value, = 2 *) in init_constants () --> set S.Adaptive_issuance.autostaking_enable false + --> set S.consensus_rights_delay consensus_rights_delay --> activate_ai `Force --> begin_test ["delegate"] --> stake "delegate" (Amount (Tez.of_mutez 1_800_000_000_000L)) --> next_cycle - --> snapshot_balances "init" ["delegate"] - --> unstake "delegate" amount - --> (* Wait [n] cycles where [0 <= n <= consensus_rights_delay + 1]. *) - List.fold_left - (fun acc i -> acc |+ Tag (fs "wait %i cycles" i) --> wait_n_cycles i) - (Tag "wait 0 cycles" --> Empty) - (Stdlib.List.init (consensus_rights_delay + 1) (fun i -> i + 1)) - --> stake "delegate" amount - --> check_snapshot_balances "init" + (* We unstake to have an amount in the last container for ufd *) + --> unstake "delegate" unstake_amount + --> next_cycle + (* We unstake either one, two or three cycles later *) + --> (Tag "unstake cycle (current-2)" + --> unstake "delegate" unstake_amount + --> next_cycle --> next_cycle + |+ Tag "unstake cycle (current-1)" --> next_cycle + --> unstake "delegate" unstake_amount + --> next_cycle + |+ Tag "unstake cycle (current)" --> next_cycle --> next_cycle + --> unstake "delegate" unstake_amount) + (* Nothing is finalizable yet. If nothing else happens, next cycle the + first unstake request will become finalizable. *) + --> check_balance_field "delegate" `Unstaked_finalizable Tez.zero + --> (Tag "stake from unstake one container" + --> stake "delegate" (Amount (Tez.of_mutez 111_000_000_000L)) + --> check_balance_field + "delegate" + `Unstaked_frozen_total + (Tez.of_mutez 333_000_000_000L) + (* We only removed unstake from the most recent unstake: we should + expect the first unstake request to finalize with its full amount on the next cycle *) + --> next_cycle + --> check_balance_field + "delegate" + `Unstaked_finalizable + (Tez.of_mutez 222_000_000_000L) + --> check_balance_field + "delegate" + `Unstaked_frozen_total + (Tez.of_mutez 111_000_000_000L) + |+ Tag "stake from unstake two containers" + --> stake "delegate" (Amount (Tez.of_mutez 333_000_000_000L)) + --> check_balance_field + "delegate" + `Unstaked_frozen_total + (Tez.of_mutez 111_000_000_000L) + (* We should have removed all the unstake from the most recent container. + The rest will be finalizable next cycle. *) + --> next_cycle + --> check_balance_field + "delegate" + `Unstaked_finalizable + (Tez.of_mutez 111_000_000_000L) + --> check_balance_field "delegate" `Unstaked_frozen_total Tez.zero + |+ Tag "stake from all unstaked + liquid" + --> stake "delegate" (Amount (Tez.of_mutez 555_000_000_000L)) + (* Nothing remains unstaked *) + --> check_balance_field "delegate" `Unstaked_frozen_total Tez.zero + --> check_balance_field "delegate" `Unstaked_finalizable Tez.zero) (* Test three different ways to finalize unstake requests: - finalize_unstake operation -- GitLab From 9739580e8c409f091951491c5665a2a0dde1d5d2 Mon Sep 17 00:00:00 2001 From: Lucas Randazzo Date: Wed, 13 Mar 2024 14:57:16 +0100 Subject: [PATCH 2/5] Proto/tests: update reward test with some overstaking --- .../test/integration/test_scenario_rewards.ml | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/proto_alpha/lib_protocol/test/integration/test_scenario_rewards.ml b/src/proto_alpha/lib_protocol/test/integration/test_scenario_rewards.ml index ab63fd640579..1450b38714b6 100644 --- a/src/proto_alpha/lib_protocol/test/integration/test_scenario_rewards.ml +++ b/src/proto_alpha/lib_protocol/test/integration/test_scenario_rewards.ml @@ -65,7 +65,7 @@ let test_wait_rewards_no_ai_yes_auto = --> next_block) (** Tests reward distribution under AI: - - with and without stakers; + - with and without stakers (sometimes overstaking); - with different values of edge. *) let test_wait_rewards_with_ai = let set_edge pct = @@ -103,13 +103,31 @@ let test_wait_rewards_with_ai = --> stake "staker2" (Amount (Tez.of_mutez 333_001_987L)) --> (Empty |+ Tag "three stakers! ha ha ha" + (* This staker overstakes *) --> add_account_with_funds "staker3" ~funder:"faucet" - (Amount (Tez.of_mutez 2_000_000_000L)) + (Amount (Tez.of_mutez 1_800_000_000_000L)) --> set_delegate "staker3" (Some "delegate") - --> stake "staker3" (Amount (Tez.of_mutez 123_456_788L)) - ))) + --> stake + "staker3" + (Amount (Tez.of_mutez 1_799_123_456_788L)) + --> exec_unit (fun (_, state) -> + let src = State.find_account "delegate" state in + let self_frozen = + src.frozen_deposits.self_current + in + let staked = + Frozen_tez.total_co_current_q + src.frozen_deposits.co_current + in + Assert.is_true + ~loc:__LOC__ + Q.( + Tez.mul_q + self_frozen + src.parameters.limit_of_staking_over_baking + < staked))))) --> set_baker "delegate" --> wait_n_cycles 20 (** Tests reward distribution under AI for one baker and one staker, -- GitLab From 0daafec8c152d12649defa9c04400f6a0f303243 Mon Sep 17 00:00:00 2001 From: Lucas Randazzo Date: Wed, 13 Mar 2024 16:15:36 +0100 Subject: [PATCH 3/5] Proto/tests: add costakers for stake from unstake Also fix test model --- .../test/helpers/state_account.ml | 11 ++++++- .../test/integration/test_scenario_stake.ml | 31 +++++++++++++++++-- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/src/proto_alpha/lib_protocol/test/helpers/state_account.ml b/src/proto_alpha/lib_protocol/test/helpers/state_account.ml index 1ac8b96ff4fa..2711986651b9 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/state_account.ml +++ b/src/proto_alpha/lib_protocol/test/helpers/state_account.ml @@ -89,7 +89,16 @@ let stake_from_unstake amount current_cycle consensus_rights_delay delegate_name let rec aux acc_unstakes rem_amount rem_unstakes = match rem_unstakes with | [] -> (acc_unstakes, rem_amount) - | (Unstaked_frozen.{initial; _} as h) :: t -> + | (Unstaked_frozen.{requests; slash_pct; _} as h) :: t -> + (* Stake from unstake cannot be called when slashing happened *) + assert (Compare.Int.(slash_pct = 0)) ; + (* This ensures initial = current for each requester. + However, the "initial" field is for the sum of all unstakes, + so cannot be used here *) + let initial = + String.Map.find_opt delegate_name requests + |> Option.value ~default:Tez.zero + in if Tez.(rem_amount = zero) then (acc_unstakes @ rem_unstakes, Tez.zero) else if Tez.(rem_amount >= initial) then 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 39e7afeb47dc..8980e9b05fe5 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 @@ -136,12 +136,33 @@ let shorter_roundtrip_for_baker = Default_parameters.constants_mainnet.consensus_rights_delay (* mainnet value, = 2 *) in + 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 --> set S.consensus_rights_delay consensus_rights_delay - --> activate_ai `Force --> begin_test ["delegate"] + --> activate_ai `Force + --> begin_test ["delegate"; "faucet"] --> stake "delegate" (Amount (Tez.of_mutez 1_800_000_000_000L)) - --> next_cycle + --> set_delegate_params "delegate" init_params + --> add_account_with_funds + "staker1" + ~funder:"faucet" + (Amount (Tez.of_mutez 200_000_000_000L)) + --> add_account_with_funds + "staker2" + ~funder:"faucet" + (Amount (Tez.of_mutez 200_000_000_000L)) + --> wait_delegate_parameters_activation --> next_cycle + --> set_delegate "staker1" (Some "delegate") + --> set_delegate "staker2" (Some "delegate") + --> stake "staker1" Half --> stake "staker2" Half + --> + (* From now on, staker1 unstakes every cycle to fill all the containers, but + this shouldn't change anything for the baker *) + let next_cycle = unstake "staker1" Half --> next_cycle in + next_cycle (* We unstake to have an amount in the last container for ufd *) --> unstake "delegate" unstake_amount --> next_cycle @@ -158,7 +179,11 @@ let shorter_roundtrip_for_baker = first unstake request will become finalizable. *) --> check_balance_field "delegate" `Unstaked_finalizable Tez.zero --> (Tag "stake from unstake one container" - --> stake "delegate" (Amount (Tez.of_mutez 111_000_000_000L)) + --> (Tag "one stake" + --> stake "delegate" (Amount (Tez.of_mutez 111_000_000_000L)) + |+ Tag "two stakes" + --> stake "delegate" (Amount (Tez.of_mutez 100_000_000_000L)) + --> stake "delegate" (Amount (Tez.of_mutez 11_000_000_000L))) --> check_balance_field "delegate" `Unstaked_frozen_total -- GitLab From ccdedbf0459962147797cec4e096b77722235d72 Mon Sep 17 00:00:00 2001 From: Lucas Randazzo Date: Wed, 13 Mar 2024 17:14:53 +0100 Subject: [PATCH 4/5] Proto/tests: add assert_success Also add optional loc argument to asserts --- .../test/helpers/scenario_base.ml | 39 ++++++++++++++----- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/src/proto_alpha/lib_protocol/test/helpers/scenario_base.ml b/src/proto_alpha/lib_protocol/test/helpers/scenario_base.ml index e98b0491d3f5..1e8d25891506 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/scenario_base.ml +++ b/src/proto_alpha/lib_protocol/test/helpers/scenario_base.ml @@ -17,6 +17,9 @@ open Log_helpers (** For [assert_failure], when expected error does not match the actual error. *) type error += Unexpected_error +(** For [assert_failure], when scenario actually succeeds when expected to fail. *) +type error += Unexpected_success + (** Usual threaded state for the tests. Contains the current block, pending operations and the known [State.t] *) type t = Block.t * State.t @@ -139,14 +142,16 @@ let check_rate_evolution (f : Q.t -> Q.t -> bool) : (t, t) scenarios = (* ======== Misc functions ========*) -let check_failure_aux ?expected_error : +let check_failure_aux ?(loc = __LOC__) ?expected_error : ('a -> 'b tzresult Lwt.t) -> 'a -> 'a tzresult Lwt.t = let open Lwt_result_syntax in fun f input -> Log.info ~color:assert_block_color "Entering failing scenario..." ; let*! output = f input in match output with - | Ok _ -> failwith "Unexpected success" + | Ok _ -> + Log.info "%s: Unexpected success@." loc ; + tzfail Unexpected_success | Error e -> ( match expected_error with | None -> @@ -159,28 +164,44 @@ let check_failure_aux ?expected_error : return input) else ( Log.info - ~color:Log.Color.FG.red - "Unexpected error:@.%a@.Expected:@.%a@." + "%s: Unexpected error:@.%a@.Expected:@.%a@." + loc (Format.pp_print_list pp) e (Format.pp_print_list pp) exp_e ; tzfail Unexpected_error)) -let check_fail_and_rollback ?expected_error : +let check_fail_and_rollback ?(loc = __LOC__) ?expected_error : ('a, 'b) single_scenario -> 'a -> 'a tzresult Lwt.t = - fun sc input -> check_failure_aux ?expected_error (run_scenario sc) input + fun sc input -> check_failure_aux ~loc ?expected_error (run_scenario sc) input (** Useful function to test expected failures: runs the given branch until it fails, then rollbacks to before execution. Fails if the given branch Succeeds *) -let assert_failure ?expected_error : ('a, 'b) scenarios -> ('a, 'a) scenarios = +let assert_failure ?(loc = __LOC__) ?expected_error : + ('a, 'b) scenarios -> ('a, 'a) scenarios = + fun scenarios -> + match unfold_scenarios scenarios with + | [] -> Empty + | [(sc, _, _)] -> exec (check_fail_and_rollback ~loc ?expected_error sc) + | _ -> + exec (fun _ -> + failwith "%s: Error: assert_failure used with branching scenario" loc) + +(** Check a scenario does not fail, and rolls back to before the assert *) +let assert_success ?(loc = __LOC__) : ('a, 'b) scenarios -> ('a, 'a) scenarios = fun scenarios -> match unfold_scenarios scenarios with | [] -> Empty - | [(sc, _, _)] -> exec (check_fail_and_rollback ?expected_error sc) + | [(sc, _, _)] -> + exec + (let open Lwt_result_syntax in + fun input -> + let* _ = run_scenario sc input in + return input) | _ -> exec (fun _ -> - failwith "Error: assert_failure used with branching scenario") + failwith "%s: Error: assert_success used with branching scenario" loc) (** Loop *) let rec loop n : ('a, 'a) scenarios -> ('a, 'a) scenarios = -- GitLab From c9354e5412e3101129c834f685fc229f9e0714d6 Mon Sep 17 00:00:00 2001 From: Lucas Randazzo Date: Wed, 13 Mar 2024 17:15:21 +0100 Subject: [PATCH 5/5] Proto/tests: add deactivation test --- .../test/integration/test_scenario_stake.ml | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) 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 8980e9b05fe5..5c14603fea50 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 @@ -409,6 +409,92 @@ let forbid_costaking = (* Now possible *) --> stake "staker" amount +(* Check that a delegate can be deactivated under AI by unstaking everything, even with stakers. + Check that such a delegate can reactivate later, and still have their stakers *) +let test_deactivation = + let init_params = + {limit_of_staking_over_baking = Q.one; edge_of_baking_over_staking = Q.one} + in + let fail_if_deactivated delegate = + let open Lwt_result_syntax in + exec_unit (fun (block, state) -> + let dlgt = State.find_account delegate state in + let* deactivated = Context.Delegate.deactivated (B block) dlgt.pkh in + Assert.is_true ~loc:__LOC__ (not deactivated)) + in + init_constants () + --> set S.Adaptive_issuance.autostaking_enable false + --> activate_ai `Force + --> begin_test ["delegate"; "faucet"] + --> stake "delegate" (Amount (Tez.of_mutez 1_800_000_000_000L)) + --> set_delegate_params "delegate" init_params + --> add_account_with_funds + "staker1" + ~funder:"faucet" + (Amount (Tez.of_mutez 200_000_000_000L)) + --> add_account_with_funds + "staker2" + ~funder:"faucet" + (Amount (Tez.of_mutez 200_000_000_000L)) + --> wait_delegate_parameters_activation --> next_cycle + --> set_delegate "staker1" (Some "delegate") + --> set_delegate "staker2" (Some "delegate") + --> stake "staker1" Half --> stake "staker2" Half --> next_cycle + (* The delegate unstakes all, starting the deactivation process *) + --> unstake "delegate" All + (* "delegate" can still bake, but not for long... *) + --> assert_success ~loc:__LOC__ (next_block_with_baker "delegate") + --> wait_n_cycles_f (fun (_, state) -> + state.State.constants.consensus_rights_delay) + (* After consensus_rights_delay, the delegate still has rights... *) + --> assert_success ~loc:__LOC__ (next_block_with_baker "delegate") + --> next_cycle + (* ...But not in the following cycle *) + --> assert_failure ~loc:__LOC__ (next_block_with_baker "delegate") + (* The stakers still have stake, and can still stake/unstake *) + --> check_balance_field "staker1" `Staked (Tez.of_mutez 100_000_000_000L) + --> check_balance_field "staker2" `Staked (Tez.of_mutez 100_000_000_000L) + --> assert_success ~loc:__LOC__ (stake "staker1" Half) + --> assert_success + ~loc:__LOC__ + (unstake "staker2" Half --> wait_n_cycles 5 + --> finalize_unstake "staker2") + (* We wait until the delegate is completely deactivated *) + --> assert_success ~loc:__LOC__ (fail_if_deactivated "delegate") + (* We already waited for [consensus_rights_delay] + 1 cycles since 0 stake, + we must wait for [consensus_rights_delay] more. *) + --> wait_n_cycles_f (fun (_, state) -> + state.State.constants.consensus_rights_delay) + --> assert_success ~loc:__LOC__ (fail_if_deactivated "delegate") + --> next_cycle + --> assert_failure ~loc:__LOC__ (fail_if_deactivated "delegate") + --> next_cycle + (* The stakers still have stake, and can still stake/unstake *) + --> check_balance_field "staker1" `Staked (Tez.of_mutez 100_000_000_000L) + --> check_balance_field "staker2" `Staked (Tez.of_mutez 100_000_000_000L) + --> assert_success ~loc:__LOC__ (stake "staker1" Half) + --> assert_success + ~loc:__LOC__ + (unstake "staker2" Half --> wait_n_cycles 5 + --> finalize_unstake "staker2") + --> next_cycle + (* We now reactivate the delegate *) + --> set_delegate "delegate" (Some "delegate") + --> stake "delegate" (Amount (Tez.of_mutez 2_000_000_000_000L)) + (* It cannot bake right away *) + --> assert_failure ~loc:__LOC__ (next_block_with_baker "delegate") + --> wait_n_cycles_f (fun (_, state) -> + state.State.constants.consensus_rights_delay) + (* After consensus_rights_delay, the delegate still has no rights... *) + --> assert_failure ~loc:__LOC__ (next_block_with_baker "delegate") + --> next_cycle + (* ...But has enough to bake in the following cycle *) + --> assert_success ~loc:__LOC__ (next_block_with_baker "delegate") + --> exec_unit (fun (_, state) -> + let dlgt = State.find_account "delegate" state in + let total = Frozen_tez.total_current dlgt.frozen_deposits in + Assert.equal_tez ~loc:__LOC__ total (Tez.of_mutez 2_200_000_000_000L)) + let tests = tests_of_scenarios @@ [ @@ -424,6 +510,7 @@ let tests = ("Test unset delegate", unset_delegate); ("Test forbid costake", forbid_costaking); ("Test stake from unstake", shorter_roundtrip_for_baker); + ("Test deactivation under AI", test_deactivation); ] let () = register_tests ~__FILE__ ~tags:["protocol"; "scenario"; "stake"] tests -- GitLab