Merge branch 'contract-better-multisigv2' into 'dev'

Better multisigv2 contract

See merge request ligolang/ligo!220
This commit is contained in:
Rémi Lesenechal 2019-11-26 22:04:43 +00:00
commit 7a7b9cc038
6 changed files with 347 additions and 72 deletions

View File

@ -75,8 +75,8 @@ let e'_bytes b : expression' result =
let e_bytes ?loc b : expression result = let e_bytes ?loc b : expression result =
let%bind e' = e'_bytes b in let%bind e' = e'_bytes b in
ok @@ location_wrap ?loc e' ok @@ location_wrap ?loc e'
let e_bytes_ofbytes ?loc (b: bytes) : expression result = let e_bytes_ofbytes ?loc (b: bytes) : expression =
ok @@ location_wrap ?loc @@ E_literal (Literal_bytes b) location_wrap ?loc @@ E_literal (Literal_bytes b)
let e_big_map ?loc lst : expression = location_wrap ?loc @@ E_big_map lst 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_record ?loc map : expression = location_wrap ?loc @@ E_record map
let e_tuple ?loc lst : expression = location_wrap ?loc @@ E_tuple lst let e_tuple ?loc lst : expression = location_wrap ?loc @@ E_tuple lst

View File

@ -61,7 +61,7 @@ val e_chain_id : ?loc:Location.t -> string -> expression
val e_mutez : ?loc:Location.t -> int -> expression val e_mutez : ?loc:Location.t -> int -> expression
val e'_bytes : string -> expression' result val e'_bytes : string -> expression' result
val e_bytes : ?loc:Location.t -> 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_big_map : ?loc:Location.t -> ( expr * expr ) list -> expression
(* (*
val e_record : ?loc:Location.t -> ( expr * expr ) list -> expression val e_record : ?loc:Location.t -> ( expr * expr ) list -> expression

View File

@ -1,46 +1,118 @@
// storage type // storage type
type threshold_t is nat 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 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 proposal_counters_t is map(address,nat)
type storage_t is record type storage_t is record
state_hash : state_hash_t ;
threshold : threshold_t ; threshold : threshold_t ;
auth : addr_set_t ; max_proposal : max_proposal_t ;
max_message_size : max_message_size_t ;
authorized_addresses : addr_set_t ;
message_store : message_store_t ; message_store : message_store_t ;
proposal_counters : proposal_counters_t ;
end end
// I/O types // I/O types
type message_t is (unit -> list(operation)) type message_t is (bytes -> list(operation))
type send_pt is message_t 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 contract_return_t is (list(operation) * storage_t)
type entry_point_t is type entry_point_t is
| Send of send_pt | 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 { 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 ; // check sender against the authorized addresses
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 ; var message : message_t := param ;
const packed_msg : bytes = bytes_pack(message) ; const packed_msg : bytes = bytes_pack(message) ;
var ret_ops : list(operation) := (nil : list(operation)) ; if size(packed_msg) > s.max_message_size then failwith("Message size exceed maximum limit")
else skip ;
var new_store : addr_set_t := // 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 case map_get(packed_msg, s.message_store) of
| Some(voters) -> set_add(sender,voters) | Some(voters) -> block { // the message is already stored
| None -> (set_empty : addr_set_t) end // increment the counter only if the sender isn't already associated with the message
; if set_mem(sender,voters) then skip
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.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.proposal_counters) ;
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 { if size(new_store) >= s.threshold then block {
remove packed_msg from map s.message_store ; remove packed_msg from map s.message_store ;
ret_ops := message(unit) ; ret_ops := message(s.state_hash) ;
// update the state hash
s.state_hash := sha_256 ( bytes_concat (s.state_hash , packed_msg) ) ;
// 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 } else
s.message_store[packed_msg] := new_store s.message_store[packed_msg] := new_store
} with ( ret_ops , s) } 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 { // 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.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
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 // the message isn't stored, ignore
} 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 function main(const param : entry_point_t; const s : storage_t) : contract_return_t is
case param of 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) | 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 end

View File

@ -22,18 +22,6 @@ let compile_main () =
open Ast_simplified 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 init_storage threshold counter pkeys =
let keys = List.map let keys = List.map
(fun el -> (fun el ->

View File

@ -22,54 +22,207 @@ let compile_main () =
open Ast_simplified 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 = let empty_op_list =
(e_typed_list [] t_operation) (e_typed_list [] t_operation)
let empty_message = e_lambda "arguments" 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 empty_op_list
let empty_message2 = e_lambda "arguments"
(Some t_bytes) (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 [ 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 {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
addr_exp::auth_set , (addr_exp, e_nat ctr)::counter_st)
([],[])
id_counter_list in
e_ez_record [
("state_hash" , e_bytes_ofbytes state_hash ) ;
("threshold" , e_nat threshold ) ; ("threshold" , e_nat threshold ) ;
("auth" , e_typed_set ("max_proposal" , e_nat max_proposal ) ;
(List.fold_left (fun acc el -> (e_address @@ addr el)::acc) [] id_list) ("max_message_size" , e_nat max_msg_size ) ;
t_address) ; ("authorized_addresses", e_typed_set auth_set t_address ) ;
("message_store" , e_typed_big_map store_list t_bytes (t_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 *) (* sender not stored in the authorized set *)
let wrong_addr () = let wrong_addr () =
let%bind program,_ = get_program () in let%bind program,_ = get_program () in
let init_storage = storage 1 [1;2] [] in let init_storage = storage {
let amount = Memory_proto_alpha.Protocol.Alpha_context.Tez.zero in threshold = 1 ; max_proposal = 1 ; max_msg_size = 1 ; state_hash = Bytes.empty ;
id_counter_list = [1,0 ; 2,0] ;
msg_store_list = []
} in
let source = contract 3 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%bind () =
let exp_failwith = "Unauthorized address" in let exp_failwith = "Unauthorized address" in
expect_string_failwith ~options program "main" 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 ; state_hash = Bytes.empty ;
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 bytes1 = e_bytes_ofbytes packed_payload1 in
let init_storage = storage {
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
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 () ok ()
(* sender message is already stored in the message store *) (* sender message is already stored in the message store *)
let already_accounted () = let send_already_accounted () =
let%bind program,_ = get_program () in let%bind program,_ = get_program () in
let%bind packed_payload = pack_payload program empty_message 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 2 [1;2] let init_storage = storage {
[(bytes, e_set [e_address@@ addr 1])] in 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
let options = let options =
let amount = Memory_proto_alpha.Protocol.Alpha_context.Tez.zero in
let source = contract 1 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 init_storage)
(* sender message isn't stored in the message store *)
let send_never_accounted () =
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 init_storage' = {
threshold = 2 ; max_proposal = 1 ; max_msg_size = 15 ; state_hash = Bytes.empty ;
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 source = contract 1 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 bytes = e_bytes_ofbytes packed_payload in
let param = withdraw_param in
let init_storage' = {
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
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 bytes = e_bytes_ofbytes packed_payload in
let param = withdraw_param in
let init_storage' = {
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
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)
(* 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
let param = withdraw_param in
let init_storage = storage {
threshold = 2 ; max_proposal = 1 ; max_msg_size = 1 ; state_hash = Bytes.empty ;
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" expect_eq ~options program "main"
(e_pair param init_storage) (e_pair empty_op_list init_storage) (e_pair param init_storage) (e_pair empty_op_list init_storage)
@ -77,23 +230,30 @@ let already_accounted () =
let succeeded_storing () = let succeeded_storing () =
let%bind program,_ = get_program () in let%bind program,_ = get_program () in
let%bind packed_payload = pack_payload program empty_message 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 ; state_hash = Bytes.empty ;
id_counter_list = [1,0 ; 2,0 ; 3,0] ;
msg_store_list = [(bytes, e_typed_set [] t_address)] ;
} in
let options = let options =
let amount = Memory_proto_alpha.Protocol.Alpha_context.Tez.zero in
let source = contract 1 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" let%bind () = expect_eq_n_trace_aux ~options [1;2] program "main"
(fun th -> (fun th ->
let init_storage = storage th [1;2;3] let init_storage = storage (init_storage th) in
[(bytes, e_typed_set [] t_address)] in ok @@ e_pair (send_param empty_message) init_storage
ok @@ e_pair param init_storage
) )
(fun th -> (fun th ->
let final_msg_store, ret = match th with let hash_after_msg = sha_256_hash (Bytes.concat Bytes.empty [Bytes.empty ; packed_payload]) in
| 1 -> [] , empty_op_list let final_id_counter, final_msg_store, ret, final_state_hash = match th with
| 2 -> [(bytes, e_set [e_address@@ addr 1])] , empty_op_list | 1 -> [1,0 ; 2,0 ; 3,0] , [] , empty_op_list , hash_after_msg
| _ -> failwith "impossible" in | 2 -> [1,1 ; 2,0 ; 3,0] , [(bytes, e_set [e_address@@ addr 1])] , empty_op_list , (init_storage th).state_hash
let final_storage = storage th [1;2;3] final_msg_store in | _ -> 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 ok @@ e_pair ret final_storage
) in ) in
ok () ok ()
@ -101,6 +261,12 @@ let succeeded_storing () =
let main = test_suite "Multisig v2" [ let main = test_suite "Multisig v2" [
test "compile" compile_main ; test "compile" compile_main ;
test "wrong_addr" wrong_addr ; test "wrong_addr" wrong_addr ;
test "already_accounted" already_accounted ; 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 "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 ;
] ]

View File

@ -49,6 +49,31 @@ let sign_message (program:Ast_typed.program) (payload : expression) sk : string
let signature_str = Signature.to_b58check signed_data in let signature_str = Signature.to_b58check signed_data in
ok signature_str 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)
let sha_256_hash pl =
let open Proto_alpha_utils.Memory_proto_alpha.Alpha_environment in
Raw_hashes.sha256 pl
open Ast_simplified.Combinators open Ast_simplified.Combinators
let expect ?options program entry_point input expecter = let expect ?options program entry_point input expecter =
@ -126,6 +151,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 let%bind _ = bind_map_list_seq aux lst in
ok () 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 expect_eq_n_aux ?options lst program entry_point make_input make_expected =
let aux n = let aux n =
let input = make_input n in let input = make_input n in