944 lines
40 KiB
OCaml
944 lines
40 KiB
OCaml
|
(*****************************************************************************)
|
||
|
(* *)
|
||
|
(* Open Source License *)
|
||
|
(* Copyright (c) 2018 Dynamic Ledger Solutions, Inc. <contact@tezos.com> *)
|
||
|
(* *)
|
||
|
(* 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 Protocol
|
||
|
open Test_utils
|
||
|
|
||
|
(* missing stuff in Alpha_context.Vote *)
|
||
|
let ballots_zero = Alpha_context.Vote.{ yay = 0l ; nay = 0l ; pass = 0l }
|
||
|
let ballots_equal b1 b2 =
|
||
|
Alpha_context.Vote.(b1.yay = b2.yay && b1.nay = b2.nay && b1.pass = b2.pass)
|
||
|
let ballots_pp ppf v = Alpha_context.Vote.(
|
||
|
Format.fprintf ppf "{ yay = %ld ; nay = %ld ; pass = %ld" v.yay v.nay v.pass)
|
||
|
|
||
|
(* constants and ratios used in voting:
|
||
|
percent_mul denotes the percent multiplier
|
||
|
initial_participation is 7000 that is, 7/10 * percent_mul
|
||
|
the participation EMA ratio pr_ema_weight / den = 7 / 10
|
||
|
the participation ratio pr_num / den = 2 / 10
|
||
|
note: we use the same denominator for both participation EMA and participation rate.
|
||
|
supermajority rate is s_num / s_den = 8 / 10 *)
|
||
|
let percent_mul = 100_00
|
||
|
let initial_participation_num = 7
|
||
|
let initial_participation = initial_participation_num * percent_mul / 10
|
||
|
let pr_ema_weight = 8
|
||
|
let den = 10
|
||
|
let pr_num = den - pr_ema_weight
|
||
|
let s_num = 8
|
||
|
let s_den = 10
|
||
|
let qr_min_num = 2
|
||
|
let qr_max_num = 7
|
||
|
let expected_qr_num =
|
||
|
Float.(of_int qr_min_num +.
|
||
|
of_int initial_participation_num *. (of_int qr_max_num -. of_int qr_min_num) /. of_int den)
|
||
|
|
||
|
(* Protocol_hash.zero is "PrihK96nBAFSxVL1GLJTVhu9YnzkMFiBeuJRPA8NwuZVZCE1L6i" *)
|
||
|
let protos = Array.map (fun s -> Protocol_hash.of_b58check_exn s)
|
||
|
[| "ProtoALphaALphaALphaALphaALphaALphaALpha61322gcLUGH" ;
|
||
|
"ProtoALphaALphaALphaALphaALphaALphaALphabc2a7ebx6WB" ;
|
||
|
"ProtoALphaALphaALphaALphaALphaALphaALpha84efbeiF6cm" ;
|
||
|
"ProtoALphaALphaALphaALphaALphaALphaALpha91249Z65tWS" ;
|
||
|
"ProtoALphaALphaALphaALphaALphaALphaALpha537f5h25LnN" ;
|
||
|
"ProtoALphaALphaALphaALphaALphaALphaALpha5c8fefgDYkr" ;
|
||
|
"ProtoALphaALphaALphaALphaALphaALphaALpha3f31feSSarC" ;
|
||
|
"ProtoALphaALphaALphaALphaALphaALphaALphabe31ahnkxSC" ;
|
||
|
"ProtoALphaALphaALphaALphaALphaALphaALphabab3bgRb7zQ" ;
|
||
|
"ProtoALphaALphaALphaALphaALphaALphaALphaf8d39cctbpk" ;
|
||
|
"ProtoALphaALphaALphaALphaALphaALphaALpha3b981byuYxD" ;
|
||
|
"ProtoALphaALphaALphaALphaALphaALphaALphaa116bccYowi" ;
|
||
|
"ProtoALphaALphaALphaALphaALphaALphaALphacce68eHqboj" ;
|
||
|
"ProtoALphaALphaALphaALphaALphaALphaALpha225c7YrWwR7" ;
|
||
|
"ProtoALphaALphaALphaALphaALphaALphaALpha58743cJL6FG" ;
|
||
|
"ProtoALphaALphaALphaALphaALphaALphaALphac91bcdvmJFR" ;
|
||
|
"ProtoALphaALphaALphaALphaALphaALphaALpha1faaadhV7oW" ;
|
||
|
"ProtoALphaALphaALphaALphaALphaALphaALpha98232gD94QJ" ;
|
||
|
"ProtoALphaALphaALphaALphaALphaALphaALpha9d1d8cijvAh" ;
|
||
|
"ProtoALphaALphaALphaALphaALphaALphaALphaeec52dKF6Gx" ;
|
||
|
"ProtoALphaALphaALphaALphaALphaALphaALpha841f2cQqajX" ; |]
|
||
|
|
||
|
(** helper functions *)
|
||
|
let mk_contracts_from_pkh pkh_list =
|
||
|
List.map (Alpha_context.Contract.implicit_contract) pkh_list
|
||
|
|
||
|
(* get the list of delegates and the list of their rolls from listings *)
|
||
|
let get_delegates_and_rolls_from_listings b =
|
||
|
Context.Vote.get_listings (B b) >>=? fun l ->
|
||
|
return ((mk_contracts_from_pkh (List.map fst l)), List.map snd l)
|
||
|
|
||
|
(* compute the rolls of each delegate *)
|
||
|
let get_rolls b delegates loc =
|
||
|
Context.Vote.get_listings (B b) >>=? fun l ->
|
||
|
map_s (fun delegate ->
|
||
|
Context.Contract.pkh delegate >>=? fun pkh ->
|
||
|
match List.find_opt (fun (del,_) -> del = pkh) l with
|
||
|
| None -> failwith "%s - Missing delegate" loc
|
||
|
| Some (_, rolls) -> return rolls
|
||
|
) delegates
|
||
|
|
||
|
let test_successful_vote num_delegates () =
|
||
|
let min_proposal_quorum = Int32.(of_int @@ 100_00 / num_delegates) in
|
||
|
Context.init ~min_proposal_quorum num_delegates >>=? fun (b,_) ->
|
||
|
Context.get_constants (B b) >>=?
|
||
|
fun { parametric = {blocks_per_voting_period ; _ } ; _ } ->
|
||
|
|
||
|
(* no ballots in proposal period *)
|
||
|
Context.Vote.get_ballots (B b) >>=? fun v ->
|
||
|
Assert.equal ~loc:__LOC__ ballots_equal "Unexpected ballots" ballots_pp
|
||
|
v ballots_zero >>=? fun () ->
|
||
|
|
||
|
(* no ballots in proposal period *)
|
||
|
Context.Vote.get_ballot_list (B b) >>=? begin function
|
||
|
| [] -> return_unit
|
||
|
| _ -> failwith "%s - Unexpected ballot list" __LOC__
|
||
|
end >>=? fun () ->
|
||
|
|
||
|
(* period 0 *)
|
||
|
Context.Vote.get_voting_period (B b) >>=? fun v ->
|
||
|
let open Alpha_context in
|
||
|
Assert.equal ~loc:__LOC__ Voting_period.equal "Unexpected period"
|
||
|
Voting_period.pp v Voting_period.(root)
|
||
|
>>=? fun () ->
|
||
|
|
||
|
Context.Vote.get_current_period_kind (B b) >>=? begin function
|
||
|
| Proposal -> return_unit
|
||
|
| _ -> failwith "%s - Unexpected period kind" __LOC__
|
||
|
end >>=? fun () ->
|
||
|
|
||
|
(* participation EMA starts at initial_participation *)
|
||
|
Context.Vote.get_participation_ema b >>=? fun v ->
|
||
|
Assert.equal_int ~loc:__LOC__ initial_participation (Int32.to_int v) >>=? fun () ->
|
||
|
|
||
|
(* listings must be populated in proposal period *)
|
||
|
Context.Vote.get_listings (B b) >>=? begin function
|
||
|
| [] -> failwith "%s - Unexpected empty listings" __LOC__
|
||
|
| _ -> return_unit
|
||
|
end >>=? fun () ->
|
||
|
|
||
|
(* beginning of proposal, denoted by _p1;
|
||
|
take a snapshot of the active delegates and their rolls from listings *)
|
||
|
get_delegates_and_rolls_from_listings b >>=? fun (delegates_p1, rolls_p1) ->
|
||
|
|
||
|
(* no proposals at the beginning of proposal period *)
|
||
|
Context.Vote.get_proposals (B b) >>=? fun ps ->
|
||
|
begin if Environment.Protocol_hash.Map.is_empty ps
|
||
|
then return_unit
|
||
|
else failwith "%s - Unexpected proposals" __LOC__
|
||
|
end >>=? fun () ->
|
||
|
|
||
|
(* no current proposal during proposal period *)
|
||
|
Context.Vote.get_current_proposal (B b) >>=? begin function
|
||
|
| None -> return_unit
|
||
|
| Some _ -> failwith "%s - Unexpected proposal" __LOC__
|
||
|
end >>=? fun () ->
|
||
|
|
||
|
let del1 = List.nth delegates_p1 0 in
|
||
|
let del2 = List.nth delegates_p1 1 in
|
||
|
let props = List.map (fun i -> protos.(i))
|
||
|
(2 -- Constants.max_proposals_per_delegate) in
|
||
|
Op.proposals (B b) del1 (Protocol_hash.zero::props) >>=? fun ops1 ->
|
||
|
Op.proposals (B b) del2 [Protocol_hash.zero] >>=? fun ops2 ->
|
||
|
Block.bake ~operations:[ops1;ops2] b >>=? fun b ->
|
||
|
|
||
|
(* proposals are now populated *)
|
||
|
Context.Vote.get_proposals (B b) >>=? fun ps ->
|
||
|
|
||
|
(* correctly count the double proposal for zero *)
|
||
|
begin
|
||
|
let weight = Int32.add (List.nth rolls_p1 0) (List.nth rolls_p1 1) in
|
||
|
match Environment.Protocol_hash.(Map.find_opt zero ps) with
|
||
|
| Some v -> if v = weight then return_unit
|
||
|
else failwith "%s - Wrong count %ld is not %ld" __LOC__ v weight
|
||
|
| None -> failwith "%s - Missing proposal" __LOC__
|
||
|
end >>=? fun () ->
|
||
|
|
||
|
(* proposing more than maximum_proposals fails *)
|
||
|
Op.proposals (B b) del1 (Protocol_hash.zero::props) >>=? fun ops ->
|
||
|
Block.bake ~operations:[ops] b >>= fun res ->
|
||
|
Assert.proto_error ~loc:__LOC__ res begin function
|
||
|
| Amendment.Too_many_proposals -> true
|
||
|
| _ -> false
|
||
|
end >>=? fun () ->
|
||
|
|
||
|
(* proposing less than one proposal fails *)
|
||
|
Op.proposals (B b) del1 [] >>=? fun ops ->
|
||
|
Block.bake ~operations:[ops] b >>= fun res ->
|
||
|
Assert.proto_error ~loc:__LOC__ res begin function
|
||
|
| Amendment.Empty_proposal -> true
|
||
|
| _ -> false
|
||
|
end >>=? fun () ->
|
||
|
|
||
|
(* skip to testing_vote period
|
||
|
-1 because we already baked one block with the proposal *)
|
||
|
Block.bake_n ((Int32.to_int blocks_per_voting_period)-2) b >>=? fun b ->
|
||
|
|
||
|
(* we moved to a testing_vote period with one proposal *)
|
||
|
Context.Vote.get_current_period_kind (B b) >>=? begin function
|
||
|
| Testing_vote -> return_unit
|
||
|
| _ -> failwith "%s - Unexpected period kind" __LOC__
|
||
|
end >>=? fun () ->
|
||
|
|
||
|
(* period 1 *)
|
||
|
Context.Vote.get_voting_period (B b) >>=? fun v ->
|
||
|
let open Alpha_context in
|
||
|
Assert.equal ~loc:__LOC__ Voting_period.equal "Unexpected period"
|
||
|
Voting_period.pp v Voting_period.(succ root)
|
||
|
>>=? fun () ->
|
||
|
|
||
|
(* listings must be populated in testing_vote period *)
|
||
|
Context.Vote.get_listings (B b) >>=? begin function
|
||
|
| [] -> failwith "%s - Unexpected empty listings" __LOC__
|
||
|
| _ -> return_unit
|
||
|
end >>=? fun () ->
|
||
|
|
||
|
(* beginning of testing_vote period, denoted by _p2;
|
||
|
take a snapshot of the active delegates and their rolls from listings *)
|
||
|
get_delegates_and_rolls_from_listings b >>=? fun (delegates_p2, rolls_p2) ->
|
||
|
|
||
|
(* no proposals during testing_vote period *)
|
||
|
Context.Vote.get_proposals (B b) >>=? fun ps ->
|
||
|
begin if Environment.Protocol_hash.Map.is_empty ps
|
||
|
then return_unit
|
||
|
else failwith "%s - Unexpected proposals" __LOC__
|
||
|
end >>=? fun () ->
|
||
|
|
||
|
(* current proposal must be set during testing_vote period *)
|
||
|
Context.Vote.get_current_proposal (B b) >>=? begin function
|
||
|
| Some v -> if Protocol_hash.(equal zero v) then return_unit
|
||
|
else failwith "%s - Wrong proposal" __LOC__
|
||
|
| None -> failwith "%s - Missing proposal" __LOC__
|
||
|
end >>=? fun () ->
|
||
|
|
||
|
(* unanimous vote: all delegates --active when p2 started-- vote *)
|
||
|
map_s (fun del ->
|
||
|
Op.ballot (B b) del Protocol_hash.zero Vote.Yay)
|
||
|
delegates_p2 >>=? fun operations ->
|
||
|
Block.bake ~operations b >>=? fun b ->
|
||
|
|
||
|
Op.ballot (B b) del1 Protocol_hash.zero Vote.Nay >>=? fun op ->
|
||
|
Block.bake ~operations:[op] b >>= fun res ->
|
||
|
Assert.proto_error ~loc:__LOC__ res begin function
|
||
|
| Amendment.Unauthorized_ballot -> true
|
||
|
| _ -> false
|
||
|
end >>=? fun () ->
|
||
|
|
||
|
fold_left_s (fun v acc -> return Int32.(add v acc))
|
||
|
0l rolls_p2 >>=? fun rolls_sum ->
|
||
|
|
||
|
(* # of Yays in ballots matches rolls of the delegate *)
|
||
|
Context.Vote.get_ballots (B b) >>=? fun v ->
|
||
|
Assert.equal ~loc:__LOC__ ballots_equal "Unexpected ballots" ballots_pp
|
||
|
v Vote.{ yay = rolls_sum ; nay = 0l ; pass = 0l } >>=? fun () ->
|
||
|
|
||
|
(* One Yay ballot per delegate *)
|
||
|
Context.Vote.get_ballot_list (B b) >>=? begin function
|
||
|
| [] -> failwith "%s - Unexpected empty ballot list" __LOC__
|
||
|
| l ->
|
||
|
iter_s (fun delegate ->
|
||
|
Context.Contract.pkh delegate >>=? fun pkh ->
|
||
|
match List.find_opt (fun (del,_) -> del = pkh) l with
|
||
|
| None -> failwith "%s - Missing delegate" __LOC__
|
||
|
| Some (_, Vote.Yay) -> return_unit
|
||
|
| Some _ -> failwith "%s - Wrong ballot" __LOC__
|
||
|
) delegates_p2
|
||
|
end >>=? fun () ->
|
||
|
|
||
|
|
||
|
(* skip to testing period
|
||
|
-1 because we already baked one block with the ballot *)
|
||
|
Block.bake_n ((Int32.to_int blocks_per_voting_period)-1) b >>=? fun b ->
|
||
|
|
||
|
Context.Vote.get_current_period_kind (B b) >>=? begin function
|
||
|
| Testing -> return_unit
|
||
|
| _ -> failwith "%s - Unexpected period kind" __LOC__
|
||
|
end >>=? fun () ->
|
||
|
|
||
|
(* period 2 *)
|
||
|
Context.Vote.get_voting_period (B b) >>=? fun v ->
|
||
|
let open Alpha_context in
|
||
|
Assert.equal ~loc:__LOC__ Voting_period.equal "Unexpected period"
|
||
|
Voting_period.pp v Voting_period.(succ (succ root))
|
||
|
>>=? fun () ->
|
||
|
|
||
|
(* no ballots in testing period *)
|
||
|
Context.Vote.get_ballots (B b) >>=? fun v ->
|
||
|
Assert.equal ~loc:__LOC__ ballots_equal "Unexpected ballots" ballots_pp
|
||
|
v ballots_zero >>=? fun () ->
|
||
|
|
||
|
(* listings must be empty in testing period *)
|
||
|
Context.Vote.get_listings (B b) >>=? begin function
|
||
|
| [] -> return_unit
|
||
|
| _ -> failwith "%s - Unexpected listings" __LOC__
|
||
|
end >>=? fun () ->
|
||
|
|
||
|
|
||
|
(* skip to promotion_vote period *)
|
||
|
Block.bake_n (Int32.to_int blocks_per_voting_period) b >>=? fun b ->
|
||
|
|
||
|
Context.Vote.get_current_period_kind (B b) >>=? begin function
|
||
|
| Promotion_vote -> return_unit
|
||
|
| _ -> failwith "%s - Unexpected period kind" __LOC__
|
||
|
end >>=? fun () ->
|
||
|
|
||
|
(* period 3 *)
|
||
|
Context.Vote.get_voting_period (B b) >>=? fun v ->
|
||
|
let open Alpha_context in
|
||
|
Assert.equal ~loc:__LOC__ Voting_period.equal "Unexpected period"
|
||
|
Voting_period.pp v Voting_period.(succ (succ (succ root)))
|
||
|
>>=? fun () ->
|
||
|
|
||
|
(* listings must be populated in promotion_vote period *)
|
||
|
Context.Vote.get_listings (B b) >>=? begin function
|
||
|
| [] -> failwith "%s - Unexpected empty listings" __LOC__
|
||
|
| _ -> return_unit
|
||
|
end >>=? fun () ->
|
||
|
|
||
|
(* beginning of promotion_vote period, denoted by _p4;
|
||
|
take a snapshot of the active delegates and their rolls from listings *)
|
||
|
get_delegates_and_rolls_from_listings b >>=? fun (delegates_p4, rolls_p4) ->
|
||
|
|
||
|
(* no proposals during promotion_vote period *)
|
||
|
Context.Vote.get_proposals (B b) >>=? fun ps ->
|
||
|
begin if Environment.Protocol_hash.Map.is_empty ps
|
||
|
then return_unit
|
||
|
else failwith "%s - Unexpected proposals" __LOC__
|
||
|
end >>=? fun () ->
|
||
|
|
||
|
(* current proposal must be set during promotion_vote period *)
|
||
|
Context.Vote.get_current_proposal (B b) >>=? begin function
|
||
|
| Some v -> if Protocol_hash.(equal zero v) then return_unit
|
||
|
else failwith "%s - Wrong proposal" __LOC__
|
||
|
| None -> failwith "%s - Missing proposal" __LOC__
|
||
|
end >>=? fun () ->
|
||
|
|
||
|
(* unanimous vote: all delegates --active when p4 started-- vote *)
|
||
|
map_s (fun del ->
|
||
|
Op.ballot (B b) del Protocol_hash.zero Vote.Yay)
|
||
|
delegates_p4 >>=? fun operations ->
|
||
|
Block.bake ~operations b >>=? fun b ->
|
||
|
|
||
|
fold_left_s (fun v acc -> return Int32.(add v acc))
|
||
|
0l rolls_p4 >>=? fun rolls_sum ->
|
||
|
|
||
|
(* # of Yays in ballots matches rolls of the delegate *)
|
||
|
Context.Vote.get_ballots (B b) >>=? fun v ->
|
||
|
Assert.equal ~loc:__LOC__ ballots_equal "Unexpected ballots" ballots_pp
|
||
|
v Vote.{ yay = rolls_sum ; nay = 0l ; pass = 0l } >>=? fun () ->
|
||
|
|
||
|
(* One Yay ballot per delegate *)
|
||
|
Context.Vote.get_ballot_list (B b) >>=? begin function
|
||
|
| [] -> failwith "%s - Unexpected empty ballot list" __LOC__
|
||
|
| l ->
|
||
|
iter_s (fun delegate ->
|
||
|
Context.Contract.pkh delegate >>=? fun pkh ->
|
||
|
match List.find_opt (fun (del,_) -> del = pkh) l with
|
||
|
| None -> failwith "%s - Missing delegate" __LOC__
|
||
|
| Some (_, Vote.Yay) -> return_unit
|
||
|
| Some _ -> failwith "%s - Wrong ballot" __LOC__
|
||
|
) delegates_p4
|
||
|
end >>=? fun () ->
|
||
|
|
||
|
(* skip to end of promotion_vote period and activation*)
|
||
|
Block.bake_n Int32.((to_int blocks_per_voting_period)-1) b >>=? fun b ->
|
||
|
|
||
|
(* zero is the new protocol (before the vote this value is unset) *)
|
||
|
Context.Vote.get_protocol b >>= fun p ->
|
||
|
Assert.equal ~loc:__LOC__ Protocol_hash.equal "Unexpected proposal"
|
||
|
Protocol_hash.pp p Protocol_hash.zero >>=? fun () ->
|
||
|
|
||
|
return_unit
|
||
|
|
||
|
(* given a list of active delegates,
|
||
|
return the first k active delegates with which one can have quorum, that is:
|
||
|
their roll sum divided by the total roll sum is bigger than pr_ema_weight/den *)
|
||
|
let get_smallest_prefix_voters_for_quorum active_delegates active_rolls =
|
||
|
fold_left_s (fun v acc -> return Int32.(add v acc))
|
||
|
0l active_rolls >>=? fun active_rolls_sum ->
|
||
|
let rec loop delegates rolls sum selected =
|
||
|
match delegates, rolls with
|
||
|
| [], [] -> selected
|
||
|
| del :: delegates, del_rolls :: rolls ->
|
||
|
if den * sum < Float.to_int (expected_qr_num *. (Int32.to_float active_rolls_sum)) then
|
||
|
loop delegates rolls (sum + (Int32.to_int del_rolls)) (del :: selected)
|
||
|
else selected
|
||
|
| _, _ -> [] in
|
||
|
return (loop active_delegates active_rolls 0 [])
|
||
|
|
||
|
let get_expected_participation_ema rolls voter_rolls old_participation_ema =
|
||
|
(* formula to compute the updated participation_ema *)
|
||
|
let get_updated_participation_ema old_participation_ema participation =
|
||
|
(pr_ema_weight * (Int32.to_int old_participation_ema) +
|
||
|
pr_num * participation) / den
|
||
|
in
|
||
|
fold_left_s (fun v acc -> return Int32.(add v acc))
|
||
|
0l rolls >>=? fun rolls_sum ->
|
||
|
fold_left_s (fun v acc -> return Int32.(add v acc))
|
||
|
0l voter_rolls >>=? fun voter_rolls_sum ->
|
||
|
let participation = (Int32.to_int voter_rolls_sum) * percent_mul /
|
||
|
(Int32.to_int rolls_sum) in
|
||
|
return (get_updated_participation_ema old_participation_ema participation)
|
||
|
|
||
|
(* if not enough quorum -- get_updated_participation_ema < pr_ema_weight/den -- in testing vote,
|
||
|
go back to proposal period *)
|
||
|
let test_not_enough_quorum_in_testing_vote num_delegates () =
|
||
|
let min_proposal_quorum = Int32.(of_int @@ 100_00 / num_delegates) in
|
||
|
Context.init ~min_proposal_quorum num_delegates >>=? fun (b,delegates) ->
|
||
|
Context.get_constants (B b) >>=?
|
||
|
fun { parametric = {blocks_per_voting_period ; _ } ; _ } ->
|
||
|
|
||
|
(* proposal period *)
|
||
|
let open Alpha_context in
|
||
|
Context.Vote.get_current_period_kind (B b) >>=? begin function
|
||
|
| Proposal -> return_unit
|
||
|
| _ -> failwith "%s - Unexpected period kind" __LOC__
|
||
|
end >>=? fun () ->
|
||
|
|
||
|
let proposer = List.nth delegates 0 in
|
||
|
Op.proposals (B b) proposer [Protocol_hash.zero] >>=? fun ops ->
|
||
|
Block.bake ~operations:[ops] b >>=? fun b ->
|
||
|
|
||
|
(* skip to vote_testing period
|
||
|
-1 because we already baked one block with the proposal *)
|
||
|
Block.bake_n ((Int32.to_int blocks_per_voting_period)-2) b >>=? fun b ->
|
||
|
|
||
|
(* we moved to a testing_vote period with one proposal *)
|
||
|
Context.Vote.get_current_period_kind (B b) >>=? begin function
|
||
|
| Testing_vote -> return_unit
|
||
|
| _ -> failwith "%s - Unexpected period kind" __LOC__
|
||
|
end >>=? fun () ->
|
||
|
|
||
|
Context.Vote.get_participation_ema b >>=? fun initial_participation_ema ->
|
||
|
(* beginning of testing_vote period, denoted by _p2;
|
||
|
take a snapshot of the active delegates and their rolls from listings *)
|
||
|
get_delegates_and_rolls_from_listings b >>=? fun (delegates_p2, rolls_p2) ->
|
||
|
|
||
|
get_smallest_prefix_voters_for_quorum delegates_p2 rolls_p2 >>=? fun voters ->
|
||
|
(* take the first two voters out so there cannot be quorum *)
|
||
|
let voters_without_quorum = List.tl voters in
|
||
|
get_rolls b voters_without_quorum __LOC__ >>=? fun voters_rolls_in_testing_vote ->
|
||
|
|
||
|
(* all voters_without_quorum vote, for yays;
|
||
|
no nays, so supermajority is satisfied *)
|
||
|
map_s (fun del ->
|
||
|
Op.ballot (B b) del Protocol_hash.zero Vote.Yay)
|
||
|
voters_without_quorum >>=? fun operations ->
|
||
|
Block.bake ~operations b >>=? fun b ->
|
||
|
|
||
|
(* skip to testing period *)
|
||
|
Block.bake_n ((Int32.to_int blocks_per_voting_period)-1) b >>=? fun b ->
|
||
|
|
||
|
(* we move back to the proposal period because not enough quorum *)
|
||
|
Context.Vote.get_current_period_kind (B b) >>=? begin function
|
||
|
| Proposal -> return_unit
|
||
|
| _ -> failwith "%s - Unexpected period kind" __LOC__
|
||
|
end >>=? fun () ->
|
||
|
|
||
|
(* check participation_ema update *)
|
||
|
get_expected_participation_ema rolls_p2
|
||
|
voters_rolls_in_testing_vote initial_participation_ema >>=? fun expected_participation_ema ->
|
||
|
Context.Vote.get_participation_ema b >>=? fun new_participation_ema ->
|
||
|
(* assert the formula to calculate participation_ema is correct *)
|
||
|
Assert.equal_int ~loc:__LOC__ expected_participation_ema
|
||
|
(Int32.to_int new_participation_ema) >>=? fun () ->
|
||
|
|
||
|
return_unit
|
||
|
|
||
|
(* if not enough quorum -- get_updated_participation_ema < pr_ema_weight/den -- in promotion vote,
|
||
|
go back to proposal period *)
|
||
|
let test_not_enough_quorum_in_promotion_vote num_delegates () =
|
||
|
let min_proposal_quorum = Int32.(of_int @@ 100_00 / num_delegates) in
|
||
|
Context.init ~min_proposal_quorum num_delegates >>=? fun (b,delegates) ->
|
||
|
Context.get_constants (B b) >>=?
|
||
|
fun { parametric = {blocks_per_voting_period ; _ } ; _ } ->
|
||
|
|
||
|
Context.Vote.get_current_period_kind (B b) >>=? begin function
|
||
|
| Proposal -> return_unit
|
||
|
| _ -> failwith "%s - Unexpected period kind" __LOC__
|
||
|
end >>=? fun () ->
|
||
|
|
||
|
let proposer = List.nth delegates 0 in
|
||
|
Op.proposals (B b) proposer (Protocol_hash.zero::[]) >>=? fun ops ->
|
||
|
Block.bake ~operations:[ops] b >>=? fun b ->
|
||
|
|
||
|
(* skip to vote_testing period
|
||
|
-1 because we already baked one block with the proposal *)
|
||
|
Block.bake_n ((Int32.to_int blocks_per_voting_period)-2) b >>=? fun b ->
|
||
|
|
||
|
(* we moved to a testing_vote period with one proposal *)
|
||
|
Context.Vote.get_current_period_kind (B b) >>=? begin function
|
||
|
| Testing_vote -> return_unit
|
||
|
| _ -> failwith "%s - Unexpected period kind" __LOC__
|
||
|
end >>=? fun () ->
|
||
|
|
||
|
(* beginning of testing_vote period, denoted by _p2;
|
||
|
take a snapshot of the active delegates and their rolls from listings *)
|
||
|
get_delegates_and_rolls_from_listings b >>=? fun (delegates_p2, rolls_p2) ->
|
||
|
|
||
|
get_smallest_prefix_voters_for_quorum delegates_p2 rolls_p2 >>=? fun voters ->
|
||
|
|
||
|
let open Alpha_context in
|
||
|
|
||
|
(* all voters vote, for yays;
|
||
|
no nays, so supermajority is satisfied *)
|
||
|
map_s (fun del ->
|
||
|
Op.ballot (B b) del Protocol_hash.zero Vote.Yay)
|
||
|
voters >>=? fun operations ->
|
||
|
|
||
|
Block.bake ~operations b >>=? fun b ->
|
||
|
|
||
|
(* skip to testing period *)
|
||
|
Block.bake_n ((Int32.to_int blocks_per_voting_period)-1) b >>=? fun b ->
|
||
|
|
||
|
(* we move to testing because we have supermajority and enough quorum *)
|
||
|
Context.Vote.get_current_period_kind (B b) >>=? begin function
|
||
|
| Testing -> return_unit
|
||
|
| _ -> failwith "%s - Unexpected period kind" __LOC__
|
||
|
end >>=? fun () ->
|
||
|
|
||
|
(* skip to promotion_vote period *)
|
||
|
Block.bake_n (Int32.to_int blocks_per_voting_period) b >>=? fun b ->
|
||
|
|
||
|
Context.Vote.get_current_period_kind (B b) >>=? begin function
|
||
|
| Promotion_vote -> return_unit
|
||
|
| _ -> failwith "%s - Unexpected period kind" __LOC__
|
||
|
end >>=? fun () ->
|
||
|
|
||
|
Context.Vote.get_participation_ema b >>=? fun initial_participation_ema ->
|
||
|
(* beginning of promotion period, denoted by _p4;
|
||
|
take a snapshot of the active delegates and their rolls from listings *)
|
||
|
get_delegates_and_rolls_from_listings b >>=? fun (delegates_p4, rolls_p4) ->
|
||
|
get_smallest_prefix_voters_for_quorum delegates_p4 rolls_p4 >>=? fun voters ->
|
||
|
|
||
|
(* take the first voter out so there cannot be quorum *)
|
||
|
let voters_without_quorum = List.tl voters in
|
||
|
get_rolls b voters_without_quorum __LOC__ >>=? fun voter_rolls ->
|
||
|
|
||
|
(* all voters_without_quorum vote, for yays;
|
||
|
no nays, so supermajority is satisfied *)
|
||
|
map_s (fun del ->
|
||
|
Op.ballot (B b) del Protocol_hash.zero Vote.Yay)
|
||
|
voters_without_quorum >>=? fun operations ->
|
||
|
Block.bake ~operations b >>=? fun b ->
|
||
|
|
||
|
(* skip to end of promotion_vote period *)
|
||
|
Block.bake_n ((Int32.to_int blocks_per_voting_period)-1) b >>=? fun b ->
|
||
|
|
||
|
get_expected_participation_ema rolls_p4 voter_rolls
|
||
|
initial_participation_ema >>=? fun expected_participation_ema ->
|
||
|
|
||
|
Context.Vote.get_participation_ema b >>=? fun new_participation_ema ->
|
||
|
|
||
|
(* assert the formula to calculate participation_ema is correct *)
|
||
|
Assert.equal_int ~loc:__LOC__ expected_participation_ema
|
||
|
(Int32.to_int new_participation_ema) >>=? fun () ->
|
||
|
|
||
|
(* we move back to the proposal period because not enough quorum *)
|
||
|
Context.Vote.get_current_period_kind (B b) >>=? begin function
|
||
|
| Proposal -> return_unit
|
||
|
| _ -> failwith "%s - Unexpected period kind" __LOC__
|
||
|
end >>=? fun () ->
|
||
|
|
||
|
return_unit
|
||
|
|
||
|
let test_multiple_identical_proposals_count_as_one () =
|
||
|
Context.init 1 >>=? fun (b,delegates) ->
|
||
|
|
||
|
Context.Vote.get_current_period_kind (B b) >>=? begin function
|
||
|
| Proposal -> return_unit
|
||
|
| _ -> failwith "%s - Unexpected period kind" __LOC__
|
||
|
end >>=? fun () ->
|
||
|
|
||
|
let proposer = List.hd delegates in
|
||
|
Op.proposals (B b) proposer
|
||
|
[Protocol_hash.zero; Protocol_hash.zero] >>=? fun ops ->
|
||
|
Block.bake ~operations:[ops] b >>=? fun b ->
|
||
|
(* compute the weight of proposals *)
|
||
|
Context.Vote.get_proposals (B b) >>=? fun ps ->
|
||
|
|
||
|
(* compute the rolls of proposer *)
|
||
|
Context.Contract.pkh proposer >>=? fun pkh ->
|
||
|
Context.Vote.get_listings (B b) >>=? fun l ->
|
||
|
begin match List.find_opt (fun (del,_) -> del = pkh) l with
|
||
|
| None -> failwith "%s - Missing delegate" __LOC__
|
||
|
| Some (_, proposer_rolls) -> return proposer_rolls
|
||
|
end >>=? fun proposer_rolls ->
|
||
|
|
||
|
(* correctly count the double proposal for zero as one proposal *)
|
||
|
let expected_weight_proposer = proposer_rolls in
|
||
|
match Environment.Protocol_hash.(Map.find_opt zero ps) with
|
||
|
| Some v -> if v = expected_weight_proposer then return_unit
|
||
|
else failwith
|
||
|
"%s - Wrong count %ld is not %ld; identical proposals count as one"
|
||
|
__LOC__ v expected_weight_proposer
|
||
|
| None -> failwith "%s - Missing proposal" __LOC__
|
||
|
|
||
|
|
||
|
(* assumes the initial balance of allocated by Context.init is at
|
||
|
least 4 time the value of the tokens_per_roll constant *)
|
||
|
let test_supermajority_in_proposal there_is_a_winner () =
|
||
|
let min_proposal_quorum = 0l in
|
||
|
Context.init ~min_proposal_quorum ~initial_balances:[1L; 1L; 1L] 10 >>=? fun (b,delegates) ->
|
||
|
Context.get_constants (B b)
|
||
|
>>=? fun { parametric = {blocks_per_cycle; blocks_per_voting_period; tokens_per_roll; _ } ; _ } ->
|
||
|
|
||
|
let del1 = List.nth delegates 0 in
|
||
|
let del2 = List.nth delegates 1 in
|
||
|
let del3 = List.nth delegates 2 in
|
||
|
|
||
|
map_s (fun del -> Context.Contract.pkh del) [del1; del2; del3] >>=? fun pkhs ->
|
||
|
let policy = Block.Excluding pkhs in
|
||
|
|
||
|
Op.transaction (B b) (List.nth delegates 3) del1 tokens_per_roll >>=? fun op1 ->
|
||
|
Op.transaction (B b) (List.nth delegates 4) del2 tokens_per_roll >>=? fun op2 ->
|
||
|
begin
|
||
|
if there_is_a_winner
|
||
|
then Test_tez.Tez.( *? ) tokens_per_roll 3L
|
||
|
else Test_tez.Tez.( *? ) tokens_per_roll 2L
|
||
|
end >>?= fun bal3 ->
|
||
|
Op.transaction (B b) (List.nth delegates 5) del3 bal3 >>=? fun op3 ->
|
||
|
|
||
|
Block.bake ~policy ~operations:[op1; op2; op3] b >>=? fun b ->
|
||
|
|
||
|
(* we let one voting period pass; we make sure that:
|
||
|
- the three selected delegates remain active by re-registering as delegates
|
||
|
- their number of rolls do not change *)
|
||
|
fold_left_s (fun b _ ->
|
||
|
Error_monad.map_s (fun del ->
|
||
|
Context.Contract.pkh del >>=? fun pkh ->
|
||
|
Op.delegation (B b) del (Some pkh)
|
||
|
) delegates >>=? fun ops ->
|
||
|
Block.bake ~policy ~operations:ops b >>=? fun b ->
|
||
|
Block.bake_until_cycle_end ~policy b
|
||
|
) b (1 --
|
||
|
(Int32.to_int (Int32.div blocks_per_voting_period blocks_per_cycle))) >>=? fun b ->
|
||
|
|
||
|
(* make the proposals *)
|
||
|
Op.proposals (B b) del1 [protos.(0)] >>=? fun ops1 ->
|
||
|
Op.proposals (B b) del2 [protos.(0)] >>=? fun ops2 ->
|
||
|
Op.proposals (B b) del3 [protos.(1)] >>=? fun ops3 ->
|
||
|
Block.bake ~policy ~operations:[ops1;ops2;ops3] b >>=? fun b ->
|
||
|
Block.bake_n ~policy ((Int32.to_int blocks_per_voting_period)-1) b >>=? fun b ->
|
||
|
|
||
|
(* we remain in the proposal period when there is no winner,
|
||
|
otherwise we move to the testing vote period *)
|
||
|
Context.Vote.get_current_period_kind (B b) >>=? begin function
|
||
|
| Testing_vote ->
|
||
|
if there_is_a_winner then return_unit
|
||
|
else failwith "%s - Expected period kind Proposal, obtained Testing_vote" __LOC__
|
||
|
| Proposal ->
|
||
|
if not there_is_a_winner then return_unit
|
||
|
else failwith "%s - Expected period kind Testing_vote, obtained Proposal" __LOC__
|
||
|
| _ -> failwith "%s - Unexpected period kind" __LOC__
|
||
|
end >>=? fun () ->
|
||
|
|
||
|
return_unit
|
||
|
|
||
|
|
||
|
let test_quorum_in_proposal has_quorum () =
|
||
|
let total_tokens = 32_000_000_000_000L in
|
||
|
let half_tokens = Int64.div total_tokens 2L in
|
||
|
Context.init
|
||
|
~initial_balances:[1L; half_tokens; half_tokens]
|
||
|
3 >>=? fun (b,delegates) ->
|
||
|
Context.get_constants (B b) >>=? fun {
|
||
|
parametric = {
|
||
|
blocks_per_cycle;
|
||
|
blocks_per_voting_period;
|
||
|
min_proposal_quorum; _ } ; _ } ->
|
||
|
|
||
|
let del1 = List.nth delegates 0 in
|
||
|
let del2 = List.nth delegates 1 in
|
||
|
|
||
|
map_s (fun del -> Context.Contract.pkh del) [del1; del2] >>=? fun pkhs ->
|
||
|
let policy = Block.Excluding pkhs in
|
||
|
|
||
|
let quorum =
|
||
|
if has_quorum then
|
||
|
Int64.of_int32 min_proposal_quorum
|
||
|
else
|
||
|
Int64.(sub (of_int32 min_proposal_quorum) 10L) in
|
||
|
let bal =
|
||
|
Int64.(div (mul total_tokens quorum) 100_00L)
|
||
|
|> Test_tez.Tez.of_mutez_exn in
|
||
|
Op.transaction (B b) del2 del1 bal >>=? fun op2 ->
|
||
|
Block.bake ~policy ~operations:[op2] b >>=? fun b ->
|
||
|
|
||
|
(* we let one voting period pass; we make sure that:
|
||
|
- the two selected delegates remain active by re-registering as delegates
|
||
|
- their number of rolls do not change *)
|
||
|
fold_left_s (fun b _ ->
|
||
|
Error_monad.map_s (fun del ->
|
||
|
Context.Contract.pkh del >>=? fun pkh ->
|
||
|
Op.delegation (B b) del (Some pkh)
|
||
|
) [del1;del2] >>=? fun ops ->
|
||
|
Block.bake ~policy ~operations:ops b >>=? fun b ->
|
||
|
Block.bake_until_cycle_end ~policy b
|
||
|
) b (1 --
|
||
|
(Int32.to_int (Int32.div blocks_per_voting_period blocks_per_cycle))) >>=? fun b ->
|
||
|
|
||
|
(* make the proposal *)
|
||
|
Op.proposals (B b) del1 [protos.(0)] >>=? fun ops ->
|
||
|
Block.bake ~policy ~operations:[ops] b >>=? fun b ->
|
||
|
Block.bake_n ~policy ((Int32.to_int blocks_per_voting_period)-1) b >>=? fun b ->
|
||
|
|
||
|
(* we remain in the proposal period when there is no quorum,
|
||
|
otherwise we move to the testing vote period *)
|
||
|
Context.Vote.get_current_period_kind (B b) >>=? begin function
|
||
|
| Testing_vote ->
|
||
|
if has_quorum then return_unit
|
||
|
else failwith "%s - Expected period kind Proposal, obtained Testing_vote" __LOC__
|
||
|
| Proposal ->
|
||
|
if not has_quorum then return_unit
|
||
|
else failwith "%s - Expected period kind Testing_vote, obtained Proposal" __LOC__
|
||
|
| _ -> failwith "%s - Unexpected period kind" __LOC__
|
||
|
end >>=? fun () ->
|
||
|
|
||
|
return_unit
|
||
|
|
||
|
|
||
|
let test_supermajority_in_testing_vote supermajority () =
|
||
|
let min_proposal_quorum = Int32.(of_int @@ 100_00 / 100) in
|
||
|
Context.init ~min_proposal_quorum 100 >>=? fun (b,delegates) ->
|
||
|
Context.get_constants (B b) >>=?
|
||
|
fun { parametric = {blocks_per_voting_period ; _ } ; _ } ->
|
||
|
|
||
|
let del1 = List.nth delegates 0 in
|
||
|
let proposal = protos.(0) in
|
||
|
|
||
|
Op.proposals (B b) del1 [proposal] >>=? fun ops1 ->
|
||
|
Block.bake ~operations:[ops1] b >>=? fun b ->
|
||
|
Block.bake_n ((Int32.to_int blocks_per_voting_period)-1) b >>=? fun b ->
|
||
|
|
||
|
(* move to testing_vote *)
|
||
|
Context.Vote.get_current_period_kind (B b) >>=? begin function
|
||
|
| Testing_vote -> return_unit
|
||
|
| _ -> failwith "%s - Unexpected period kind" __LOC__
|
||
|
end >>=? fun () ->
|
||
|
|
||
|
(* assert our proposal won *)
|
||
|
Context.Vote.get_current_proposal (B b) >>=? begin function
|
||
|
| Some v -> if Protocol_hash.(equal proposal v) then return_unit
|
||
|
else failwith "%s - Wrong proposal" __LOC__
|
||
|
| None -> failwith "%s - Missing proposal" __LOC__
|
||
|
end >>=? fun () ->
|
||
|
|
||
|
(* beginning of testing_vote period, denoted by _p2;
|
||
|
take a snapshot of the active delegates and their rolls from listings *)
|
||
|
get_delegates_and_rolls_from_listings b >>=? fun (delegates_p2, _olls_p2) ->
|
||
|
|
||
|
(* supermajority means [num_yays / (num_yays + num_nays) >= s_num / s_den],
|
||
|
which is equivalent with [num_yays >= num_nays * s_num / (s_den - s_num)] *)
|
||
|
let num_delegates = List.length delegates_p2 in
|
||
|
let num_nays = num_delegates / 5 in (* any smaller number will do as well *)
|
||
|
let num_yays = num_nays * s_num / (s_den - s_num) in
|
||
|
(* majority/minority vote depending on the [supermajority] parameter *)
|
||
|
let num_yays = if supermajority then num_yays else num_yays - 1 in
|
||
|
|
||
|
let open Alpha_context in
|
||
|
|
||
|
let nays_delegates, rest = List.split_n num_nays delegates_p2 in
|
||
|
let yays_delegates, _ = List.split_n num_yays rest in
|
||
|
map_s (fun del ->
|
||
|
Op.ballot (B b) del proposal Vote.Yay)
|
||
|
yays_delegates >>=? fun operations_yays ->
|
||
|
map_s (fun del ->
|
||
|
Op.ballot (B b) del proposal Vote.Nay)
|
||
|
nays_delegates >>=? fun operations_nays ->
|
||
|
let operations = operations_yays @ operations_nays in
|
||
|
|
||
|
Block.bake ~operations b >>=? fun b ->
|
||
|
Block.bake_n ((Int32.to_int blocks_per_voting_period)-1) b >>=? fun b ->
|
||
|
|
||
|
Context.Vote.get_current_period_kind (B b) >>=? begin function
|
||
|
| Testing ->
|
||
|
if supermajority then return_unit
|
||
|
else failwith "%s - Expected period kind Proposal, obtained Testing" __LOC__
|
||
|
| Proposal ->
|
||
|
if not supermajority then return_unit
|
||
|
else failwith "%s - Expected period kind Testing_vote, obtained Proposal" __LOC__
|
||
|
| _ -> failwith "%s - Unexpected period kind" __LOC__
|
||
|
end >>=? fun () ->
|
||
|
|
||
|
return_unit
|
||
|
|
||
|
(* test also how the selection scales: all delegates propose max proposals *)
|
||
|
let test_no_winning_proposal num_delegates () =
|
||
|
let min_proposal_quorum = Int32.(of_int @@ 100_00 / num_delegates) in
|
||
|
Context.init ~min_proposal_quorum num_delegates >>=? fun (b,_) ->
|
||
|
Context.get_constants (B b) >>=?
|
||
|
fun { parametric = {blocks_per_voting_period ; _ } ; _ } ->
|
||
|
|
||
|
(* beginning of proposal, denoted by _p1;
|
||
|
take a snapshot of the active delegates and their rolls from listings *)
|
||
|
get_delegates_and_rolls_from_listings b >>=? fun (delegates_p1, _rolls_p1) ->
|
||
|
|
||
|
let open Alpha_context in
|
||
|
let props = List.map (fun i -> protos.(i))
|
||
|
(1 -- Constants.max_proposals_per_delegate) in
|
||
|
(* all delegates active in p1 propose the same proposals *)
|
||
|
map_s
|
||
|
(fun del -> Op.proposals (B b) del props)
|
||
|
delegates_p1 >>=? fun ops_list ->
|
||
|
Block.bake ~operations:ops_list b >>=? fun b ->
|
||
|
|
||
|
(* skip to testing_vote period
|
||
|
-1 because we already baked one block with the proposal *)
|
||
|
Block.bake_n ((Int32.to_int blocks_per_voting_period)-2) b >>=? fun b ->
|
||
|
|
||
|
(* we stay in the same proposal period because no winning proposal *)
|
||
|
Context.Vote.get_current_period_kind (B b) >>=? begin function
|
||
|
| Proposal -> return_unit
|
||
|
| _ -> failwith "%s - Unexpected period kind" __LOC__
|
||
|
end >>=? fun () ->
|
||
|
|
||
|
return_unit
|
||
|
|
||
|
(** Test that for the vote to pass with maximum possible participation_ema
|
||
|
(100%), it is sufficient for the vote quorum to be equal or greater than
|
||
|
the maximum quorum cap. *)
|
||
|
let test_quorum_capped_maximum num_delegates () =
|
||
|
let min_proposal_quorum = Int32.(of_int @@ 100_00 / num_delegates) in
|
||
|
Context.init ~min_proposal_quorum num_delegates >>=? fun (b,delegates) ->
|
||
|
(* set the participation EMA to 100% *)
|
||
|
Context.Vote.set_participation_ema b 100_00l >>= fun b ->
|
||
|
Context.get_constants (B b) >>=?
|
||
|
fun { parametric = { blocks_per_voting_period ; quorum_max ; _ } ; _ } ->
|
||
|
|
||
|
(* proposal period *)
|
||
|
let open Alpha_context in
|
||
|
Context.Vote.get_current_period_kind (B b) >>=? begin function
|
||
|
| Proposal -> return_unit
|
||
|
| _ -> failwith "%s - Unexpected period kind" __LOC__
|
||
|
end >>=? fun () ->
|
||
|
|
||
|
(* propose a new protocol *)
|
||
|
let protocol = Protocol_hash.zero in
|
||
|
let proposer = List.nth delegates 0 in
|
||
|
Op.proposals (B b) proposer [protocol] >>=? fun ops ->
|
||
|
Block.bake ~operations:[ops] b >>=? fun b ->
|
||
|
|
||
|
(* skip to vote_testing period
|
||
|
-1 because we already baked one block with the proposal *)
|
||
|
Block.bake_n ((Int32.to_int blocks_per_voting_period)-1) b >>=? fun b ->
|
||
|
|
||
|
(* we moved to a testing_vote period with one proposal *)
|
||
|
Context.Vote.get_current_period_kind (B b) >>=? begin function
|
||
|
| Testing_vote -> return_unit
|
||
|
| _ -> failwith "%s - Unexpected period kind" __LOC__
|
||
|
end >>=? fun () ->
|
||
|
|
||
|
(* take percentage of the delegates equal or greater than quorum_max *)
|
||
|
let minimum_to_pass =
|
||
|
Float.of_int (List.length delegates) *. Int32.(to_float quorum_max) /. 100_00.
|
||
|
|> Float.ceil
|
||
|
|> Float.to_int
|
||
|
in
|
||
|
let voters = List.take_n minimum_to_pass delegates in
|
||
|
(* all voters vote for yays; no nays, so supermajority is satisfied *)
|
||
|
map_s (fun del ->
|
||
|
Op.ballot (B b) del protocol Vote.Yay)
|
||
|
voters >>=? fun operations ->
|
||
|
Block.bake ~operations b >>=? fun b ->
|
||
|
|
||
|
(* skip to next period *)
|
||
|
Block.bake_n ((Int32.to_int blocks_per_voting_period)-1) b >>=? fun b ->
|
||
|
|
||
|
(* expect to move to testing because we have supermajority and enough quorum *)
|
||
|
Context.Vote.get_current_period_kind (B b) >>=? begin function
|
||
|
| Testing -> return_unit
|
||
|
| _ -> failwith "%s - Unexpected period kind" __LOC__
|
||
|
end
|
||
|
|
||
|
(** Test that for the vote to pass with minimum possible participation_ema
|
||
|
(0%), it is sufficient for the vote quorum to be equal or greater than
|
||
|
the minimum quorum cap. *)
|
||
|
let test_quorum_capped_minimum num_delegates () =
|
||
|
let min_proposal_quorum = Int32.(of_int @@ 100_00 / num_delegates) in
|
||
|
Context.init ~min_proposal_quorum num_delegates >>=? fun (b,delegates) ->
|
||
|
(* set the participation EMA to 0% *)
|
||
|
Context.Vote.set_participation_ema b 0l >>= fun b ->
|
||
|
Context.get_constants (B b) >>=?
|
||
|
fun { parametric = { blocks_per_voting_period ; quorum_min ; _ } ; _ } ->
|
||
|
|
||
|
(* proposal period *)
|
||
|
let open Alpha_context in
|
||
|
Context.Vote.get_current_period_kind (B b) >>=? begin function
|
||
|
| Proposal -> return_unit
|
||
|
| _ -> failwith "%s - Unexpected period kind" __LOC__
|
||
|
end >>=? fun () ->
|
||
|
|
||
|
(* propose a new protocol *)
|
||
|
let protocol = Protocol_hash.zero in
|
||
|
let proposer = List.nth delegates 0 in
|
||
|
Op.proposals (B b) proposer [protocol] >>=? fun ops ->
|
||
|
Block.bake ~operations:[ops] b >>=? fun b ->
|
||
|
|
||
|
(* skip to vote_testing period
|
||
|
-1 because we already baked one block with the proposal *)
|
||
|
Block.bake_n ((Int32.to_int blocks_per_voting_period)-1) b >>=? fun b ->
|
||
|
|
||
|
(* we moved to a testing_vote period with one proposal *)
|
||
|
Context.Vote.get_current_period_kind (B b) >>=? begin function
|
||
|
| Testing_vote -> return_unit
|
||
|
| _ -> failwith "%s - Unexpected period kind" __LOC__
|
||
|
end >>=? fun () ->
|
||
|
|
||
|
(* take percentage of the delegates equal or greater than quorum_min *)
|
||
|
let minimum_to_pass =
|
||
|
Float.of_int (List.length delegates) *. Int32.(to_float quorum_min) /. 100_00.
|
||
|
|> Float.ceil
|
||
|
|> Float.to_int
|
||
|
in
|
||
|
let voters = List.take_n minimum_to_pass delegates in
|
||
|
(* all voters vote for yays; no nays, so supermajority is satisfied *)
|
||
|
map_s (fun del ->
|
||
|
Op.ballot (B b) del protocol Vote.Yay)
|
||
|
voters >>=? fun operations ->
|
||
|
Block.bake ~operations b >>=? fun b ->
|
||
|
|
||
|
(* skip to next period *)
|
||
|
Block.bake_n ((Int32.to_int blocks_per_voting_period)-1) b >>=? fun b ->
|
||
|
|
||
|
(* expect to move to testing because we have supermajority and enough quorum *)
|
||
|
Context.Vote.get_current_period_kind (B b) >>=? begin function
|
||
|
| Testing -> return_unit
|
||
|
| _ -> failwith "%s - Unexpected period kind" __LOC__
|
||
|
end
|
||
|
|
||
|
let tests = [
|
||
|
Test.tztest "voting successful_vote" `Quick (test_successful_vote 137) ;
|
||
|
Test.tztest "voting testing vote, not enough quorum" `Quick (test_not_enough_quorum_in_testing_vote 245) ;
|
||
|
Test.tztest "voting promotion vote, not enough quorum" `Quick (test_not_enough_quorum_in_promotion_vote 432) ;
|
||
|
Test.tztest "voting counting double proposal" `Quick test_multiple_identical_proposals_count_as_one;
|
||
|
Test.tztest "voting proposal, with supermajority" `Quick (test_supermajority_in_proposal true) ;
|
||
|
Test.tztest "voting proposal, without supermajority" `Quick (test_supermajority_in_proposal false) ;
|
||
|
Test.tztest "voting proposal, with quorum" `Quick (test_quorum_in_proposal true) ;
|
||
|
Test.tztest "voting proposal, without quorum" `Quick (test_quorum_in_proposal false) ;
|
||
|
Test.tztest "voting testing vote, with supermajority" `Quick (test_supermajority_in_testing_vote true) ;
|
||
|
Test.tztest "voting testing vote, without supermajority" `Quick (test_supermajority_in_testing_vote false) ;
|
||
|
Test.tztest "voting proposal, no winning proposal" `Quick (test_no_winning_proposal 400) ;
|
||
|
Test.tztest "voting quorum, quorum capped maximum" `Quick (test_quorum_capped_maximum 400) ;
|
||
|
Test.tztest "voting quorum, quorum capped minimum" `Quick (test_quorum_capped_minimum 401) ;
|
||
|
]
|