From 00def6d20e3ad25d5517f1b3407591ab1c43f5a8 Mon Sep 17 00:00:00 2001 From: Lesenechal Remi Date: Mon, 25 Nov 2019 12:13:55 +0100 Subject: [PATCH 1/7] Fix a bug, test added: A never accounted message was not adding anything to the map --- src/test/contracts/multisig-v2.ligo | 2 +- src/test/multisig_v2_tests.ml | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/test/contracts/multisig-v2.ligo b/src/test/contracts/multisig-v2.ligo index 7815fcbcf..273df6176 100644 --- a/src/test/contracts/multisig-v2.ligo +++ b/src/test/contracts/multisig-v2.ligo @@ -29,7 +29,7 @@ function send (const param : send_pt; const s : storage_t) : contract_return_t i var new_store : addr_set_t := case map_get(packed_msg, s.message_store) of | Some(voters) -> set_add(sender,voters) - | None -> (set_empty : addr_set_t) end + | None -> set sender end end ; if size(new_store) >= s.threshold then block { diff --git a/src/test/multisig_v2_tests.ml b/src/test/multisig_v2_tests.ml index fcd006641..f70536c08 100644 --- a/src/test/multisig_v2_tests.ml +++ b/src/test/multisig_v2_tests.ml @@ -73,6 +73,22 @@ let already_accounted () = expect_eq ~options program "main" (e_pair param init_storage) (e_pair empty_op_list init_storage) +(* sender message isn't stored in the message store *) +let never_accounted () = + let%bind program,_ = get_program () in + let%bind packed_payload = pack_payload program empty_message in + let%bind bytes = e_bytes_ofbytes packed_payload in + let init_storage = storage 2 [1;2] + [] in + let final_storage = storage 2 [1;2] + [(bytes, e_set [e_address@@ addr 1])] in + let options = + let amount = Memory_proto_alpha.Protocol.Alpha_context.Tez.zero in + let source = contract 1 in + Proto_alpha_utils.Memory_proto_alpha.make_options ~amount ~source () in + expect_eq ~options program "main" + (e_pair param init_storage) (e_pair empty_op_list final_storage) + (* successful storing in the message store *) let succeeded_storing () = let%bind program,_ = get_program () in @@ -102,5 +118,6 @@ let main = test_suite "Multisig v2" [ test "compile" compile_main ; test "wrong_addr" wrong_addr ; test "already_accounted" already_accounted ; + test "never_accounted" never_accounted ; test "succeeded_storing" succeeded_storing ; ] From 652138b11555ce92dbdeab1ae306f3e55c3e97d4 Mon Sep 17 00:00:00 2001 From: Lesenechal Remi Date: Mon, 25 Nov 2019 13:00:21 +0100 Subject: [PATCH 2/7] new 'expect' test helpers working on expressions --- src/test/test_helpers.ml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/test/test_helpers.ml b/src/test/test_helpers.ml index 612fc2fe2..5d07541a6 100644 --- a/src/test/test_helpers.ml +++ b/src/test/test_helpers.ml @@ -126,6 +126,30 @@ let expect_eq_n_trace_aux ?options lst program entry_point make_input make_expec let%bind _ = bind_map_list_seq aux lst in ok () +let expect_eq_exp_trace_aux ?options explst program entry_point make_input make_expected = + let aux exp = + let%bind input = make_input exp in + let%bind expected = make_expected exp in + let pps = Format.asprintf "%a" Ast_simplified.PP.expression exp in + trace (simple_error ("expect_eq_exp " ^ pps )) @@ + let result = expect_eq ?options program entry_point input expected in + result + in + let%bind _ = bind_map_list_seq aux explst in + ok () + +let expect_failwith_exp_trace_aux ?options explst program entry_point make_input make_expected_failwith = + let aux exp = + let%bind input = make_input exp in + let%bind expected = make_expected_failwith exp in + let pps = Format.asprintf "%a" Ast_simplified.PP.expression exp in + trace (simple_error ("expect_eq_exp " ^ pps )) @@ + let result = expect_string_failwith ?options program entry_point input expected in + result + in + let%bind _ = bind_map_list_seq aux explst in + ok () + let expect_eq_n_aux ?options lst program entry_point make_input make_expected = let aux n = let input = make_input n in From b82383751c6f5347b236802a960f73f28c72c32a Mon Sep 17 00:00:00 2001 From: Lesenechal Remi Date: Tue, 26 Nov 2019 10:37:25 +0100 Subject: [PATCH 3/7] multisigv2 more robust to spam Set limits to the number of approvals and message sizes --- src/test/contracts/multisig-v2.ligo | 60 +++++++- src/test/multisig_tests.ml | 12 -- src/test/multisig_v2_tests.ml | 217 ++++++++++++++++++++++------ src/test/test_helpers.ml | 21 +++ 4 files changed, 242 insertions(+), 68 deletions(-) diff --git a/src/test/contracts/multisig-v2.ligo b/src/test/contracts/multisig-v2.ligo index 273df6176..027335e3c 100644 --- a/src/test/contracts/multisig-v2.ligo +++ b/src/test/contracts/multisig-v2.ligo @@ -1,46 +1,90 @@ // storage type type threshold_t is nat +type max_proposal_t is nat +type max_message_size_t is nat type addr_set_t is set(address) -type message_store_t is big_map(bytes,addr_set_t) +type message_store_t is map(bytes,addr_set_t) +type counter_store_t is map(address,nat) type storage_t is record threshold : threshold_t ; + max_proposal : max_proposal_t ; + max_message_size : max_message_size_t ; auth : addr_set_t ; message_store : message_store_t ; + counter_store : counter_store_t ; end // I/O types type message_t is (unit -> list(operation)) type send_pt is message_t +type withdraw_pt is message_t type contract_return_t is (list(operation) * storage_t) type entry_point_t is | Send of send_pt +| Withdraw of withdraw_pt function send (const param : send_pt; const s : storage_t) : contract_return_t is block { if not set_mem(sender,s.auth) then failwith("Unauthorized address") else skip ; var message : message_t := param ; - const packed_msg : bytes = bytes_pack(message) ; + var new_store : addr_set_t := set_empty ; var ret_ops : list(operation) := (nil : list(operation)) ; - var new_store : addr_set_t := - case map_get(packed_msg, s.message_store) of - | Some(voters) -> set_add(sender,voters) - | None -> set sender end end - ; + const packed_msg : bytes = bytes_pack(message) ; + if size(packed_msg) > s.max_message_size then failwith("Message size exceed maximum limit") + else skip ; + + case map_get(packed_msg, s.message_store) of + | Some(voters) -> block { + if set_mem(sender,voters) then skip + else s.counter_store[sender] := get_force(sender,s.counter_store) + 1n ; + new_store := set_add(sender,voters) + } + | None -> block { + s.counter_store[sender] := get_force(sender,s.counter_store) + 1n ; + new_store := set [sender]; + } + end ; + + var sender_proposal_counter : nat := get_force(sender,s.counter_store) ; + if sender_proposal_counter > s.max_proposal then failwith("Maximum number of proposal reached") + else skip ; if size(new_store) >= s.threshold then block { remove packed_msg from map s.message_store ; ret_ops := message(unit) ; + s.counter_store[sender] := abs (sender_proposal_counter - 1n) ; } else s.message_store[packed_msg] := new_store - + } with ( ret_ops , s) +function withdraw (const param : withdraw_pt; const s : storage_t) : contract_return_t is block { + + var message : message_t := param ; + const packed_msg : bytes = bytes_pack(message) ; + + case map_get(packed_msg, s.message_store) of + | Some(voters) -> block { + const new_set : addr_set_t = set_remove(sender,voters) ; + + if size(voters) =/= size(new_set) then + s.counter_store[sender] := abs (get_force(sender,s.counter_store) - 1n) + else skip ; + + if size(new_set) = 0n then remove packed_msg from map s.message_store + else s.message_store[packed_msg] := new_set + } + | None -> skip end + +} with ( (nil: list(operation)) , s) + function main(const param : entry_point_t; const s : storage_t) : contract_return_t is case param of | Send (p) -> send(p,s) + | Withdraw (p) -> withdraw(p,s) end \ No newline at end of file diff --git a/src/test/multisig_tests.ml b/src/test/multisig_tests.ml index 7091254e1..6cde4a1ab 100644 --- a/src/test/multisig_tests.ml +++ b/src/test/multisig_tests.ml @@ -22,18 +22,6 @@ let compile_main () = open Ast_simplified -let gen_keys = fun () -> - let open Tezos_crypto in - let (raw_pkh,raw_pk,raw_sk) = Signature.generate_key () in - (raw_pkh,raw_pk,raw_sk) - -let str_keys (raw_pkh, raw_pk, raw_sk) = - let open Tezos_crypto in - let sk_str = Signature.Secret_key.to_b58check raw_sk in - let pk_str = Signature.Public_key.to_b58check raw_pk in - let pkh_str = Signature.Public_key_hash.to_b58check raw_pkh in - (pkh_str,pk_str,sk_str) - let init_storage threshold counter pkeys = let keys = List.map (fun el -> diff --git a/src/test/multisig_v2_tests.ml b/src/test/multisig_v2_tests.ml index f70536c08..65ef32641 100644 --- a/src/test/multisig_v2_tests.ml +++ b/src/test/multisig_v2_tests.ml @@ -22,102 +22,223 @@ let compile_main () = open Ast_simplified -let contract id = - let open Proto_alpha_utils.Memory_proto_alpha in - let id = List.nth dummy_environment.identities id in - id.implicit_contract -let addr id = - let open Proto_alpha_utils.Memory_proto_alpha in - Protocol.Alpha_context.Contract.to_b58check @@ contract id - let empty_op_list = (e_typed_list [] t_operation) let empty_message = e_lambda "arguments" (Some t_unit) (Some (t_list t_operation)) empty_op_list +let empty_message2 = e_lambda "arguments" + (Some t_unit) (Some (t_list t_operation)) + ( e_let_in ("foo",Some t_unit) (e_unit ()) empty_op_list) -let param = e_constructor "Send" empty_message +let send_param msg = e_constructor "Send" msg +let withdraw_param = e_constructor "Withdraw" empty_message -let storage threshold id_list store_list = e_ez_record [ - ("threshold", e_nat threshold) ; - ("auth" , e_typed_set - (List.fold_left (fun acc el -> (e_address @@ addr el)::acc) [] id_list) - t_address) ; - ("message_store" , e_typed_big_map store_list t_bytes (t_set t_address)) -] +type st_type = { + threshold:int ; + max_proposal:int ; + max_msg_size:int ; + id_counter_list: (int * int) list ; + msg_store_list: (expression * expression) list ; +} +let storage {threshold ; max_proposal ; max_msg_size ; id_counter_list ; msg_store_list} = + let auth_set,counter_store = List.fold_left + (fun (auth_set,counter_st) (id,ctr) -> + let addr_exp = e_address @@ addr id in + addr_exp::auth_set , (addr_exp, e_nat ctr)::counter_st) + ([],[]) + id_counter_list in + e_ez_record [ + ("threshold" , e_nat threshold ) ; + ("max_proposal" , e_nat max_proposal ) ; + ("max_message_size", e_nat max_msg_size ) ; + ("auth" , e_typed_set auth_set t_address ) ; + ("message_store" , e_typed_map msg_store_list t_bytes (t_set t_address) ) ; + ("counter_store" , e_typed_map counter_store t_address t_nat ) ; + ] (* sender not stored in the authorized set *) let wrong_addr () = let%bind program,_ = get_program () in - let init_storage = storage 1 [1;2] [] in - let amount = Memory_proto_alpha.Protocol.Alpha_context.Tez.zero in + let init_storage = storage { + threshold = 1 ; max_proposal = 1 ; max_msg_size = 1 ; + id_counter_list = [1,0 ; 2,0] ; + msg_store_list = [] + } in let source = contract 3 in - let options = Proto_alpha_utils.Memory_proto_alpha.make_options ~amount ~source () in + let options = Proto_alpha_utils.Memory_proto_alpha.make_options ~source () in let%bind () = let exp_failwith = "Unauthorized address" in expect_string_failwith ~options program "main" - (e_pair param init_storage) exp_failwith in + (e_pair (send_param empty_message) init_storage) exp_failwith in + ok () + +(* send a message which exceed the size limit *) +let message_size_exceeded () = + let%bind program,_ = get_program () in + let init_storage = storage { + threshold = 1 ; max_proposal = 1 ; max_msg_size = 1 ; + id_counter_list = [1,0] ; + msg_store_list = [] + } in + let source = contract 1 in + let options = Proto_alpha_utils.Memory_proto_alpha.make_options ~source () in + let%bind () = + let exp_failwith = "Message size exceed maximum limit" in + expect_string_failwith ~options program "main" + (e_pair (send_param empty_message) init_storage) exp_failwith in + ok () + +(* sender has already has reached maximum number of proposal *) +let maximum_number_of_proposal () = + let%bind program,_ = get_program () in + let%bind packed_payload1 = pack_payload program (send_param empty_message) in + let%bind bytes1 = e_bytes_ofbytes packed_payload1 in + let init_storage = storage { + threshold = 1 ; max_proposal = 1 ; max_msg_size = 15 ; + id_counter_list = [1,1] ; + msg_store_list = [(bytes1, e_set [e_address@@ addr 1])] + } in + let source = contract 1 in + let options = Proto_alpha_utils.Memory_proto_alpha.make_options ~source () in + let%bind () = + let exp_failwith = "Maximum number of proposal reached" in + expect_string_failwith ~options program "main" + (e_pair (send_param empty_message2) init_storage) exp_failwith in ok () (* sender message is already stored in the message store *) -let already_accounted () = +let send_already_accounted () = let%bind program,_ = get_program () in let%bind packed_payload = pack_payload program empty_message in let%bind bytes = e_bytes_ofbytes packed_payload in - let init_storage = storage 2 [1;2] - [(bytes, e_set [e_address@@ addr 1])] in + let init_storage = storage { + threshold = 2 ; max_proposal = 1 ; max_msg_size = 15 ; + id_counter_list = [1,1 ; 2,0] ; + msg_store_list = [(bytes, e_set [e_address@@ addr 1])] + } in let options = - let amount = Memory_proto_alpha.Protocol.Alpha_context.Tez.zero in let source = contract 1 in - Proto_alpha_utils.Memory_proto_alpha.make_options ~amount ~source () in + Proto_alpha_utils.Memory_proto_alpha.make_options ~source () in expect_eq ~options program "main" - (e_pair param init_storage) (e_pair empty_op_list init_storage) + (e_pair (send_param empty_message) init_storage) (e_pair empty_op_list init_storage) (* sender message isn't stored in the message store *) -let never_accounted () = +let send_never_accounted () = let%bind program,_ = get_program () in let%bind packed_payload = pack_payload program empty_message in let%bind bytes = e_bytes_ofbytes packed_payload in - let init_storage = storage 2 [1;2] - [] in - let final_storage = storage 2 [1;2] - [(bytes, e_set [e_address@@ addr 1])] in + let init_storage' = { + threshold = 2 ; max_proposal = 1 ; max_msg_size = 15 ; + id_counter_list = [1,0 ; 2,0] ; + msg_store_list = [] + } in + let init_storage = storage init_storage' in + let final_storage = storage { init_storage' with + id_counter_list = [1,1 ; 2,0] ; + msg_store_list = [(bytes, e_set [e_address@@ addr 1])] ; + } in let options = - let amount = Memory_proto_alpha.Protocol.Alpha_context.Tez.zero in let source = contract 1 in - Proto_alpha_utils.Memory_proto_alpha.make_options ~amount ~source () in + Proto_alpha_utils.Memory_proto_alpha.make_options ~source () in + expect_eq ~options program "main" + (e_pair (send_param empty_message) init_storage) (e_pair empty_op_list final_storage) + +(* sender withdraw message is already binded to one address in the message store *) +let withdraw_already_accounted_one () = + let%bind program,_ = get_program () in + let%bind packed_payload = pack_payload program empty_message in + let%bind bytes = e_bytes_ofbytes packed_payload in + let param = withdraw_param in + let init_storage' = { + threshold = 2 ; max_proposal = 1 ; max_msg_size = 1 ; + id_counter_list = [1,1 ; 2,0] ; + msg_store_list = [(bytes, e_set [e_address@@ addr 1])] ; + } in + let init_storage = storage init_storage' in + let final_storage = storage { init_storage' with + id_counter_list = [1,0 ; 2,0] ; + msg_store_list = [] } in + let options = + let source = contract 1 in + Proto_alpha_utils.Memory_proto_alpha.make_options ~source () in expect_eq ~options program "main" (e_pair param init_storage) (e_pair empty_op_list final_storage) +(* sender withdraw message is already binded to two addresses in the message store *) +let withdraw_already_accounted_two () = + let%bind program,_ = get_program () in + let%bind packed_payload = pack_payload program empty_message in + let%bind bytes = e_bytes_ofbytes packed_payload in + let param = withdraw_param in + let init_storage' = { + threshold = 2 ; max_proposal = 2 ; max_msg_size = 1 ; + id_counter_list = [1,1 ; 2,1] ; + msg_store_list = [(bytes, e_set [e_address@@ addr 1; e_address@@ addr 2])] ; + } in + let init_storage = storage init_storage' in + let final_storage = storage { init_storage' with + id_counter_list = [1,0 ; 2,1] ; + msg_store_list = [(bytes, e_set [e_address@@ addr 2])] } in + let options = + let source = contract 1 in + Proto_alpha_utils.Memory_proto_alpha.make_options ~source () in + expect_eq ~options program "main" + (e_pair param init_storage) (e_pair empty_op_list final_storage) + +(* sender withdraw message was never accounted *) +let withdraw_never_accounted () = + let%bind program,_ = get_program () in + let param = withdraw_param in + let init_storage = storage { + threshold = 2 ; max_proposal = 1 ; max_msg_size = 1 ; + id_counter_list = [1,0 ; 2,0] ; + msg_store_list = [] ; + } in + let options = + let source = contract 1 in + Proto_alpha_utils.Memory_proto_alpha.make_options ~source () in + expect_eq ~options program "main" + (e_pair param init_storage) (e_pair empty_op_list init_storage) + (* successful storing in the message store *) let succeeded_storing () = let%bind program,_ = get_program () in let%bind packed_payload = pack_payload program empty_message in let%bind bytes = e_bytes_ofbytes packed_payload in + let init_storage th = { + threshold = th ; max_proposal = 1 ; max_msg_size = 15 ; + id_counter_list = [1,0 ; 2,0 ; 3,0] ; + msg_store_list = [(bytes, e_typed_set [] t_address)] ; + } in let options = - let amount = Memory_proto_alpha.Protocol.Alpha_context.Tez.zero in let source = contract 1 in - Proto_alpha_utils.Memory_proto_alpha.make_options ~amount ~source () in + Proto_alpha_utils.Memory_proto_alpha.make_options ~source () in let%bind () = expect_eq_n_trace_aux ~options [1;2] program "main" (fun th -> - let init_storage = storage th [1;2;3] - [(bytes, e_typed_set [] t_address)] in - ok @@ e_pair param init_storage + let init_storage = storage (init_storage th) in + ok @@ e_pair (send_param empty_message) init_storage ) (fun th -> - let final_msg_store, ret = match th with - | 1 -> [] , empty_op_list - | 2 -> [(bytes, e_set [e_address@@ addr 1])] , empty_op_list - | _ -> failwith "impossible" in - let final_storage = storage th [1;2;3] final_msg_store in + let final_id_counter, final_msg_store, ret = match th with + | 1 -> [1,0 ; 2,0 ; 3,0] , [] , empty_op_list + | 2 -> [1,1 ; 2,0 ; 3,0] , [(bytes, e_set [e_address@@ addr 1])] , empty_op_list + | _ -> assert false in + let final_storage = storage { (init_storage th) with + msg_store_list = final_msg_store ; + id_counter_list = final_id_counter } in ok @@ e_pair ret final_storage ) in ok () let main = test_suite "Multisig v2" [ - test "compile" compile_main ; - test "wrong_addr" wrong_addr ; - test "already_accounted" already_accounted ; - test "never_accounted" never_accounted ; - test "succeeded_storing" succeeded_storing ; + test "compile" compile_main ; + test "wrong_addr" wrong_addr ; + test "message_size_exceeded" message_size_exceeded ; + test "maximum_number_of_proposal" maximum_number_of_proposal ; + test "send_already_accounted" send_already_accounted ; + test "send_never_accounted" send_never_accounted ; + test "succeeded_storing" succeeded_storing ; + test "withdraw_already_accounted_one" withdraw_already_accounted_one ; + test "withdraw_already_accounted_two" withdraw_already_accounted_two ; ] diff --git a/src/test/test_helpers.ml b/src/test/test_helpers.ml index 5d07541a6..955626dd4 100644 --- a/src/test/test_helpers.ml +++ b/src/test/test_helpers.ml @@ -49,6 +49,27 @@ let sign_message (program:Ast_typed.program) (payload : expression) sk : string let signature_str = Signature.to_b58check signed_data in ok signature_str +let contract id = + let open Proto_alpha_utils.Memory_proto_alpha in + let id = List.nth dummy_environment.identities id in + id.implicit_contract + +let addr id = + let open Proto_alpha_utils.Memory_proto_alpha in + Protocol.Alpha_context.Contract.to_b58check @@ contract id + +let gen_keys = fun () -> + let open Tezos_crypto in + let (raw_pkh,raw_pk,raw_sk) = Signature.generate_key () in + (raw_pkh,raw_pk,raw_sk) + +let str_keys (raw_pkh, raw_pk, raw_sk) = + let open Tezos_crypto in + let sk_str = Signature.Secret_key.to_b58check raw_sk in + let pk_str = Signature.Public_key.to_b58check raw_pk in + let pkh_str = Signature.Public_key_hash.to_b58check raw_pkh in + (pkh_str,pk_str,sk_str) + open Ast_simplified.Combinators let expect ?options program entry_point input expecter = From c8eb1a3fa6e39e7bec8b1f740a0e1eadec9f4528 Mon Sep 17 00:00:00 2001 From: Lesenechal Remi Date: Tue, 26 Nov 2019 11:51:49 +0100 Subject: [PATCH 4/7] multisigv2 with less user uncertainty --- src/stages/ast_simplified/combinators.ml | 4 +- src/stages/ast_simplified/combinators.mli | 2 +- src/test/contracts/multisig-v2.ligo | 7 +++- src/test/multisig_v2_tests.ml | 48 ++++++++++++----------- src/test/test_helpers.ml | 4 ++ 5 files changed, 38 insertions(+), 27 deletions(-) diff --git a/src/stages/ast_simplified/combinators.ml b/src/stages/ast_simplified/combinators.ml index 4ea4e535a..95ae05e05 100644 --- a/src/stages/ast_simplified/combinators.ml +++ b/src/stages/ast_simplified/combinators.ml @@ -75,8 +75,8 @@ let e'_bytes b : expression' result = let e_bytes ?loc b : expression result = let%bind e' = e'_bytes b in ok @@ location_wrap ?loc e' -let e_bytes_ofbytes ?loc (b: bytes) : expression result = - ok @@ location_wrap ?loc @@ E_literal (Literal_bytes b) +let e_bytes_ofbytes ?loc (b: bytes) : expression = + location_wrap ?loc @@ E_literal (Literal_bytes b) let e_big_map ?loc lst : expression = location_wrap ?loc @@ E_big_map lst let e_record ?loc map : expression = location_wrap ?loc @@ E_record map let e_tuple ?loc lst : expression = location_wrap ?loc @@ E_tuple lst diff --git a/src/stages/ast_simplified/combinators.mli b/src/stages/ast_simplified/combinators.mli index cb8a27e76..4382e7da5 100644 --- a/src/stages/ast_simplified/combinators.mli +++ b/src/stages/ast_simplified/combinators.mli @@ -61,7 +61,7 @@ val e_chain_id : ?loc:Location.t -> string -> expression val e_mutez : ?loc:Location.t -> int -> expression val e'_bytes : string -> expression' result val e_bytes : ?loc:Location.t -> string -> expression result -val e_bytes_ofbytes : ?loc:Location.t -> bytes -> expression result +val e_bytes_ofbytes : ?loc:Location.t -> bytes -> expression val e_big_map : ?loc:Location.t -> ( expr * expr ) list -> expression (* val e_record : ?loc:Location.t -> ( expr * expr ) list -> expression diff --git a/src/test/contracts/multisig-v2.ligo b/src/test/contracts/multisig-v2.ligo index 027335e3c..f28d577f7 100644 --- a/src/test/contracts/multisig-v2.ligo +++ b/src/test/contracts/multisig-v2.ligo @@ -2,11 +2,13 @@ type threshold_t is nat type max_proposal_t is nat type max_message_size_t is nat +type state_hash_t is bytes type addr_set_t is set(address) type message_store_t is map(bytes,addr_set_t) type counter_store_t is map(address,nat) type storage_t is record + state_hash : state_hash_t ; threshold : threshold_t ; max_proposal : max_proposal_t ; max_message_size : max_message_size_t ; @@ -16,7 +18,7 @@ type storage_t is record end // I/O types -type message_t is (unit -> list(operation)) +type message_t is (bytes -> list(operation)) type send_pt is message_t type withdraw_pt is message_t @@ -56,7 +58,8 @@ function send (const param : send_pt; const s : storage_t) : contract_return_t i if size(new_store) >= s.threshold then block { remove packed_msg from map s.message_store ; - ret_ops := message(unit) ; + ret_ops := message(s.state_hash) ; + s.state_hash := sha_256 ( bytes_concat (s.state_hash , packed_msg) ) ; s.counter_store[sender] := abs (sender_proposal_counter - 1n) ; } else s.message_store[packed_msg] := new_store diff --git a/src/test/multisig_v2_tests.ml b/src/test/multisig_v2_tests.ml index 65ef32641..018567278 100644 --- a/src/test/multisig_v2_tests.ml +++ b/src/test/multisig_v2_tests.ml @@ -25,23 +25,24 @@ open Ast_simplified let empty_op_list = (e_typed_list [] t_operation) let empty_message = e_lambda "arguments" - (Some t_unit) (Some (t_list t_operation)) + (Some t_bytes) (Some (t_list t_operation)) empty_op_list let empty_message2 = e_lambda "arguments" - (Some t_unit) (Some (t_list t_operation)) + (Some t_bytes) (Some (t_list t_operation)) ( e_let_in ("foo",Some t_unit) (e_unit ()) empty_op_list) let send_param msg = e_constructor "Send" msg let withdraw_param = e_constructor "Withdraw" empty_message type st_type = { + state_hash : bytes ; threshold:int ; max_proposal:int ; max_msg_size:int ; id_counter_list: (int * int) list ; msg_store_list: (expression * expression) list ; } -let storage {threshold ; max_proposal ; max_msg_size ; id_counter_list ; msg_store_list} = +let storage {state_hash ; threshold ; max_proposal ; max_msg_size ; id_counter_list ; msg_store_list} = let auth_set,counter_store = List.fold_left (fun (auth_set,counter_st) (id,ctr) -> let addr_exp = e_address @@ addr id in @@ -49,11 +50,12 @@ let storage {threshold ; max_proposal ; max_msg_size ; id_counter_list ; msg_sto ([],[]) id_counter_list in e_ez_record [ + ("state_hash" , e_bytes_ofbytes state_hash ) ; ("threshold" , e_nat threshold ) ; ("max_proposal" , e_nat max_proposal ) ; ("max_message_size", e_nat max_msg_size ) ; ("auth" , e_typed_set auth_set t_address ) ; - ("message_store" , e_typed_map msg_store_list t_bytes (t_set t_address) ) ; + ("message_store" , e_typed_map msg_store_list t_bytes (t_set t_address) ) ; ("counter_store" , e_typed_map counter_store t_address t_nat ) ; ] @@ -61,7 +63,7 @@ let storage {threshold ; max_proposal ; max_msg_size ; id_counter_list ; msg_sto let wrong_addr () = let%bind program,_ = get_program () in let init_storage = storage { - threshold = 1 ; max_proposal = 1 ; max_msg_size = 1 ; + threshold = 1 ; max_proposal = 1 ; max_msg_size = 1 ; state_hash = Bytes.empty ; id_counter_list = [1,0 ; 2,0] ; msg_store_list = [] } in @@ -77,7 +79,7 @@ let wrong_addr () = let message_size_exceeded () = let%bind program,_ = get_program () in let init_storage = storage { - threshold = 1 ; max_proposal = 1 ; max_msg_size = 1 ; + threshold = 1 ; max_proposal = 1 ; max_msg_size = 1 ; state_hash = Bytes.empty ; id_counter_list = [1,0] ; msg_store_list = [] } in @@ -93,9 +95,9 @@ let message_size_exceeded () = let maximum_number_of_proposal () = let%bind program,_ = get_program () in let%bind packed_payload1 = pack_payload program (send_param empty_message) in - let%bind bytes1 = e_bytes_ofbytes packed_payload1 in + let bytes1 = e_bytes_ofbytes packed_payload1 in let init_storage = storage { - threshold = 1 ; max_proposal = 1 ; max_msg_size = 15 ; + threshold = 1 ; max_proposal = 1 ; max_msg_size = 15 ; state_hash = Bytes.empty ; id_counter_list = [1,1] ; msg_store_list = [(bytes1, e_set [e_address@@ addr 1])] } in @@ -111,9 +113,9 @@ let maximum_number_of_proposal () = let send_already_accounted () = let%bind program,_ = get_program () in let%bind packed_payload = pack_payload program empty_message in - let%bind bytes = e_bytes_ofbytes packed_payload in + let bytes = e_bytes_ofbytes packed_payload in let init_storage = storage { - threshold = 2 ; max_proposal = 1 ; max_msg_size = 15 ; + threshold = 2 ; max_proposal = 1 ; max_msg_size = 15 ; state_hash = Bytes.empty ; id_counter_list = [1,1 ; 2,0] ; msg_store_list = [(bytes, e_set [e_address@@ addr 1])] } in @@ -127,9 +129,9 @@ let send_already_accounted () = let send_never_accounted () = let%bind program,_ = get_program () in let%bind packed_payload = pack_payload program empty_message in - let%bind bytes = e_bytes_ofbytes packed_payload in + let bytes = e_bytes_ofbytes packed_payload in let init_storage' = { - threshold = 2 ; max_proposal = 1 ; max_msg_size = 15 ; + threshold = 2 ; max_proposal = 1 ; max_msg_size = 15 ; state_hash = Bytes.empty ; id_counter_list = [1,0 ; 2,0] ; msg_store_list = [] } in @@ -148,10 +150,10 @@ let send_never_accounted () = let withdraw_already_accounted_one () = let%bind program,_ = get_program () in let%bind packed_payload = pack_payload program empty_message in - let%bind bytes = e_bytes_ofbytes packed_payload in + let bytes = e_bytes_ofbytes packed_payload in let param = withdraw_param in let init_storage' = { - threshold = 2 ; max_proposal = 1 ; max_msg_size = 1 ; + threshold = 2 ; max_proposal = 1 ; max_msg_size = 1 ; state_hash = Bytes.empty ; id_counter_list = [1,1 ; 2,0] ; msg_store_list = [(bytes, e_set [e_address@@ addr 1])] ; } in @@ -169,10 +171,10 @@ let withdraw_already_accounted_one () = let withdraw_already_accounted_two () = let%bind program,_ = get_program () in let%bind packed_payload = pack_payload program empty_message in - let%bind bytes = e_bytes_ofbytes packed_payload in + let bytes = e_bytes_ofbytes packed_payload in let param = withdraw_param in let init_storage' = { - threshold = 2 ; max_proposal = 2 ; max_msg_size = 1 ; + threshold = 2 ; max_proposal = 2 ; max_msg_size = 1 ; state_hash = Bytes.empty ; id_counter_list = [1,1 ; 2,1] ; msg_store_list = [(bytes, e_set [e_address@@ addr 1; e_address@@ addr 2])] ; } in @@ -191,7 +193,7 @@ let withdraw_never_accounted () = let%bind program,_ = get_program () in let param = withdraw_param in let init_storage = storage { - threshold = 2 ; max_proposal = 1 ; max_msg_size = 1 ; + threshold = 2 ; max_proposal = 1 ; max_msg_size = 1 ; state_hash = Bytes.empty ; id_counter_list = [1,0 ; 2,0] ; msg_store_list = [] ; } in @@ -205,9 +207,9 @@ let withdraw_never_accounted () = let succeeded_storing () = let%bind program,_ = get_program () in let%bind packed_payload = pack_payload program empty_message in - let%bind bytes = e_bytes_ofbytes packed_payload in + let bytes = e_bytes_ofbytes packed_payload in let init_storage th = { - threshold = th ; max_proposal = 1 ; max_msg_size = 15 ; + threshold = th ; max_proposal = 1 ; max_msg_size = 15 ; state_hash = Bytes.empty ; id_counter_list = [1,0 ; 2,0 ; 3,0] ; msg_store_list = [(bytes, e_typed_set [] t_address)] ; } in @@ -220,11 +222,13 @@ let succeeded_storing () = ok @@ e_pair (send_param empty_message) init_storage ) (fun th -> - let final_id_counter, final_msg_store, ret = match th with - | 1 -> [1,0 ; 2,0 ; 3,0] , [] , empty_op_list - | 2 -> [1,1 ; 2,0 ; 3,0] , [(bytes, e_set [e_address@@ addr 1])] , empty_op_list + let hash_after_msg = sha_256_hash (Bytes.concat Bytes.empty [Bytes.empty ; packed_payload]) in + let final_id_counter, final_msg_store, ret, final_state_hash = match th with + | 1 -> [1,0 ; 2,0 ; 3,0] , [] , empty_op_list , hash_after_msg + | 2 -> [1,1 ; 2,0 ; 3,0] , [(bytes, e_set [e_address@@ addr 1])] , empty_op_list , (init_storage th).state_hash | _ -> assert false in let final_storage = storage { (init_storage th) with + state_hash = final_state_hash ; msg_store_list = final_msg_store ; id_counter_list = final_id_counter } in ok @@ e_pair ret final_storage diff --git a/src/test/test_helpers.ml b/src/test/test_helpers.ml index 955626dd4..9f10cce7e 100644 --- a/src/test/test_helpers.ml +++ b/src/test/test_helpers.ml @@ -70,6 +70,10 @@ let str_keys (raw_pkh, raw_pk, raw_sk) = let pkh_str = Signature.Public_key_hash.to_b58check raw_pkh in (pkh_str,pk_str,sk_str) +let sha_256_hash pl = + let open Proto_alpha_utils.Memory_proto_alpha.Alpha_environment in + Raw_hashes.sha256 pl + open Ast_simplified.Combinators let expect ?options program entry_point input expecter = From cf540a1a78a9cb762bb12e9c1db6bf08d28f8e78 Mon Sep 17 00:00:00 2001 From: Lesenechal Remi Date: Tue, 26 Nov 2019 12:14:42 +0100 Subject: [PATCH 5/7] multisigv2 default entry point --- src/test/contracts/multisig-v2.ligo | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/test/contracts/multisig-v2.ligo b/src/test/contracts/multisig-v2.ligo index f28d577f7..41fa2c8b6 100644 --- a/src/test/contracts/multisig-v2.ligo +++ b/src/test/contracts/multisig-v2.ligo @@ -21,12 +21,14 @@ end type message_t is (bytes -> list(operation)) type send_pt is message_t type withdraw_pt is message_t +type default_pt is unit type contract_return_t is (list(operation) * storage_t) type entry_point_t is | Send of send_pt | Withdraw of withdraw_pt +| Default of default_pt function send (const param : send_pt; const s : storage_t) : contract_return_t is block { @@ -86,8 +88,12 @@ function withdraw (const param : withdraw_pt; const s : storage_t) : contract_re } with ( (nil: list(operation)) , s) +function default (const p : default_pt; const s : storage_t) : contract_return_t is + ((nil: list(operation)) , s) + function main(const param : entry_point_t; const s : storage_t) : contract_return_t is case param of - | Send (p) -> send(p,s) + | Send (p) -> send(p,s) | Withdraw (p) -> withdraw(p,s) + | Default (p) -> default(p,s) end \ No newline at end of file From 39c7766b08c8986c54412c5cb4a625a684afd538 Mon Sep 17 00:00:00 2001 From: Lesenechal Remi Date: Tue, 26 Nov 2019 13:59:00 +0100 Subject: [PATCH 6/7] multisig v2 comments --- src/test/contracts/multisig-v2.ligo | 33 +++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/src/test/contracts/multisig-v2.ligo b/src/test/contracts/multisig-v2.ligo index 41fa2c8b6..9c024fb90 100644 --- a/src/test/contracts/multisig-v2.ligo +++ b/src/test/contracts/multisig-v2.ligo @@ -30,38 +30,47 @@ type entry_point_t is | Withdraw of withdraw_pt | Default of default_pt + function send (const param : send_pt; const s : storage_t) : contract_return_t is block { + // check sender against the authorized addresses if not set_mem(sender,s.auth) then failwith("Unauthorized address") else skip ; - - var message : message_t := param ; - var new_store : addr_set_t := set_empty ; - var ret_ops : list(operation) := (nil : list(operation)) ; + // check message size against the stored limit + var message : message_t := param ; const packed_msg : bytes = bytes_pack(message) ; if size(packed_msg) > s.max_message_size then failwith("Message size exceed maximum limit") else skip ; + // compute the new set of addresses associated with the message and update counters + var new_store : addr_set_t := set_empty ; case map_get(packed_msg, s.message_store) of - | Some(voters) -> block { + | Some(voters) -> block { // the message is already stored + // increment the counter only if the sender isn't already associated with the message if set_mem(sender,voters) then skip else s.counter_store[sender] := get_force(sender,s.counter_store) + 1n ; + new_store := set_add(sender,voters) - } - | None -> block { + } + | None -> block { // the message has never been received before s.counter_store[sender] := get_force(sender,s.counter_store) + 1n ; new_store := set [sender]; } end ; + // check sender counters against the maximum number of proposal var sender_proposal_counter : nat := get_force(sender,s.counter_store) ; if sender_proposal_counter > s.max_proposal then failwith("Maximum number of proposal reached") else skip ; + // check the threshold + var ret_ops : list(operation) := (nil : list(operation)) ; if size(new_store) >= s.threshold then block { remove packed_msg from map s.message_store ; ret_ops := message(s.state_hash) ; + // update the state hash s.state_hash := sha_256 ( bytes_concat (s.state_hash , packed_msg) ) ; + // reset the counter s.counter_store[sender] := abs (sender_proposal_counter - 1n) ; } else s.message_store[packed_msg] := new_store @@ -74,17 +83,19 @@ function withdraw (const param : withdraw_pt; const s : storage_t) : contract_re const packed_msg : bytes = bytes_pack(message) ; case map_get(packed_msg, s.message_store) of - | Some(voters) -> block { + | Some(voters) -> block { // the message is stored const new_set : addr_set_t = set_remove(sender,voters) ; + // decrement the counter only if the sender was already associated with the message if size(voters) =/= size(new_set) then s.counter_store[sender] := abs (get_force(sender,s.counter_store) - 1n) else skip ; + // if the message is left without any associated addresses, remove the corresponding message_store field if size(new_set) = 0n then remove packed_msg from map s.message_store else s.message_store[packed_msg] := new_set } - | None -> skip end + | None -> skip end // the message isn't stored, ignore } with ( (nil: list(operation)) , s) @@ -93,7 +104,11 @@ function default (const p : default_pt; const s : storage_t) : contract_return_t function main(const param : entry_point_t; const s : storage_t) : contract_return_t is case param of + // propagate message p if the number authorized addresses having + // voted for the same message p equals the threshold | Send (p) -> send(p,s) + // withraw vote for message p | Withdraw (p) -> withdraw(p,s) + // use this entry-point to transfer tez to the contract | Default (p) -> default(p,s) end \ No newline at end of file From e7195c4c4155ad0917211d5395e896e821198631 Mon Sep 17 00:00:00 2001 From: Lesenechal Remi Date: Tue, 26 Nov 2019 19:21:51 +0100 Subject: [PATCH 7/7] mutisig v2 some renaming and counter reset fix --- src/test/contracts/multisig-v2.ligo | 24 ++++++++++-------- src/test/multisig_v2_tests.ml | 38 +++++++++++++++++++++++------ 2 files changed, 45 insertions(+), 17 deletions(-) diff --git a/src/test/contracts/multisig-v2.ligo b/src/test/contracts/multisig-v2.ligo index 9c024fb90..81b3536d4 100644 --- a/src/test/contracts/multisig-v2.ligo +++ b/src/test/contracts/multisig-v2.ligo @@ -5,16 +5,16 @@ type max_message_size_t is nat type state_hash_t is bytes type addr_set_t is set(address) type message_store_t is map(bytes,addr_set_t) -type counter_store_t is map(address,nat) +type proposal_counters_t is map(address,nat) type storage_t is record state_hash : state_hash_t ; threshold : threshold_t ; max_proposal : max_proposal_t ; max_message_size : max_message_size_t ; - auth : addr_set_t ; + authorized_addresses : addr_set_t ; message_store : message_store_t ; - counter_store : counter_store_t ; + proposal_counters : proposal_counters_t ; end // I/O types @@ -34,7 +34,7 @@ type entry_point_t is function send (const param : send_pt; const s : storage_t) : contract_return_t is block { // check sender against the authorized addresses - if not set_mem(sender,s.auth) then failwith("Unauthorized address") else skip ; + if not set_mem(sender,s.authorized_addresses) then failwith("Unauthorized address") else skip ; // check message size against the stored limit var message : message_t := param ; @@ -48,18 +48,18 @@ function send (const param : send_pt; const s : storage_t) : contract_return_t i | Some(voters) -> block { // the message is already stored // increment the counter only if the sender isn't already associated with the message if set_mem(sender,voters) then skip - else s.counter_store[sender] := get_force(sender,s.counter_store) + 1n ; + else s.proposal_counters[sender] := get_force(sender,s.proposal_counters) + 1n ; new_store := set_add(sender,voters) } | None -> block { // the message has never been received before - s.counter_store[sender] := get_force(sender,s.counter_store) + 1n ; + s.proposal_counters[sender] := get_force(sender,s.proposal_counters) + 1n ; new_store := set [sender]; } end ; // check sender counters against the maximum number of proposal - var sender_proposal_counter : nat := get_force(sender,s.counter_store) ; + var sender_proposal_counter : nat := get_force(sender,s.proposal_counters) ; if sender_proposal_counter > s.max_proposal then failwith("Maximum number of proposal reached") else skip ; @@ -70,8 +70,12 @@ function send (const param : send_pt; const s : storage_t) : contract_return_t i ret_ops := message(s.state_hash) ; // update the state hash s.state_hash := sha_256 ( bytes_concat (s.state_hash , packed_msg) ) ; - // reset the counter - s.counter_store[sender] := abs (sender_proposal_counter - 1n) ; + // decrement the counters + for addr -> ctr in map s.proposal_counters block { + if set_mem(addr,new_store) then + s.proposal_counters[addr] := abs (ctr - 1n) + else skip ; + } } else s.message_store[packed_msg] := new_store @@ -88,7 +92,7 @@ function withdraw (const param : withdraw_pt; const s : storage_t) : contract_re // decrement the counter only if the sender was already associated with the message if size(voters) =/= size(new_set) then - s.counter_store[sender] := abs (get_force(sender,s.counter_store) - 1n) + s.proposal_counters[sender] := abs (get_force(sender,s.proposal_counters) - 1n) else skip ; // if the message is left without any associated addresses, remove the corresponding message_store field diff --git a/src/test/multisig_v2_tests.ml b/src/test/multisig_v2_tests.ml index 018567278..3e8054402 100644 --- a/src/test/multisig_v2_tests.ml +++ b/src/test/multisig_v2_tests.ml @@ -50,13 +50,13 @@ let storage {state_hash ; threshold ; max_proposal ; max_msg_size ; id_counter_l ([],[]) id_counter_list in e_ez_record [ - ("state_hash" , e_bytes_ofbytes state_hash ) ; - ("threshold" , e_nat threshold ) ; - ("max_proposal" , e_nat max_proposal ) ; - ("max_message_size", e_nat max_msg_size ) ; - ("auth" , e_typed_set auth_set t_address ) ; - ("message_store" , e_typed_map msg_store_list t_bytes (t_set t_address) ) ; - ("counter_store" , e_typed_map counter_store t_address t_nat ) ; + ("state_hash" , e_bytes_ofbytes state_hash ) ; + ("threshold" , e_nat threshold ) ; + ("max_proposal" , e_nat max_proposal ) ; + ("max_message_size" , e_nat max_msg_size ) ; + ("authorized_addresses", e_typed_set auth_set t_address ) ; + ("message_store" , e_typed_map msg_store_list t_bytes (t_set t_address) ) ; + ("proposal_counters" , e_typed_map counter_store t_address t_nat ) ; ] (* sender not stored in the authorized set *) @@ -188,6 +188,29 @@ let withdraw_already_accounted_two () = expect_eq ~options program "main" (e_pair param init_storage) (e_pair empty_op_list final_storage) +(* triggers the threshold and check that all the participants get their counters decremented *) +let counters_reset () = + let%bind program,_ = get_program () in + let%bind packed_payload = pack_payload program empty_message in + let bytes = e_bytes_ofbytes packed_payload in + let param = send_param empty_message in + let hash_after_msg = sha_256_hash (Bytes.concat Bytes.empty [Bytes.empty ; packed_payload]) in + let init_storage' = { + threshold = 3 ; max_proposal = 2 ; max_msg_size = 15 ; state_hash = Bytes.empty ; + id_counter_list = [1,1 ; 2,1 ; 3,0] ; + msg_store_list = [(bytes, e_set [e_address@@ addr 1; e_address@@ addr 2])] ; + } in + let init_storage = storage init_storage' in + let final_storage = storage { init_storage' with + state_hash = hash_after_msg ; + id_counter_list = [1,0 ; 2,0 ; 3,0] ; + msg_store_list = [] } in + let options = + let source = contract 3 in + Proto_alpha_utils.Memory_proto_alpha.make_options ~source () in + expect_eq ~options program "main" + (e_pair param init_storage) (e_pair empty_op_list final_storage) + (* sender withdraw message was never accounted *) let withdraw_never_accounted () = let%bind program,_ = get_program () in @@ -245,4 +268,5 @@ let main = test_suite "Multisig v2" [ test "succeeded_storing" succeeded_storing ; test "withdraw_already_accounted_one" withdraw_already_accounted_one ; test "withdraw_already_accounted_two" withdraw_already_accounted_two ; + test "counters_reset" counters_reset ; ]