123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278(*****************************************************************************)(* *)(* Open Source License *)(* Copyright (c) 2019-2021 Nomadic Labs <contact@nomadic-labs.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. *)(* *)(*****************************************************************************)openProtocol_client_contextopenProtocolopenAlpha_contextopenMichelson_v1_helperstypeerror+=Contract_has_no_scriptofContract_hash.ttypeerror+=Not_a_supported_multisig_contractofScript_expr_hash.ttypeerror+=Contract_has_no_storageofContract_hash.ttypeerror+=Contract_has_unexpected_storageofContract_hash.ttypeerror+=Invalid_signatureofsignaturetypeerror+=Not_enough_signaturesofint*inttypeerror+=Action_deserialisation_errorofScript.exprtypeerror+=Bytes_deserialisation_errorofBytes.ttypeerror+=Bad_deserialized_contractof(Contract_hash.t*Contract_hash.t)typeerror+=Bad_deserialized_counterof{received:Z.t;expected:Z.t}typeerror+=Non_positive_thresholdofinttypeerror+=Threshold_too_highofint*inttypeerror+=Unsupported_feature_generic_callofScript.exprtypeerror+=Unsupported_feature_generic_call_tyofScript.exprtypeerror+=Unsupported_feature_lambdaofstringtypeerror+=|Ill_typed_argumentofContract.t*Entrypoint.t*Script.expr*Script.exprtypeerror+=Ill_typed_lambdaofScript.expr*Script.exprlet()=register_error_kind`Permanent~id:"contractHasNoScript"~title:"The given contract is not a multisig contract because it has no script"~description:"A multisig command has referenced a scriptless smart contract instead \
of a multisig smart contract."~pp:(funppfcontract->Format.fprintfppf"Contract has no script %a."Contract_hash.ppcontract)Data_encoding.(obj1(req"contract"Contract.originated_encoding))(functionContract_has_no_scriptc->Somec|_->None)(func->Contract_has_no_scriptc);register_error_kind`Permanent~id:"notASupportedMultisigContract"~title:"The given contract is not one of the supported contracts"~description:"A multisig command has referenced a smart contract whose script is not \
one of the known multisig contract scripts."~pp:(funppfhash->Format.fprintfppf"Not a supported multisig contract.@\n\
The hash of this script is %a, it was not found among in the list of \
known multisig script hashes."Script_expr_hash.pphash)Data_encoding.(obj1(req"hash"Script_expr_hash.encoding))(functionNot_a_supported_multisig_contracth->Someh|_->None)(funh->Not_a_supported_multisig_contracth);register_error_kind`Permanent~id:"contractHasNoStorage"~title:"The given contract is not a multisig contract because it has no storage"~description:"A multisig command has referenced a smart contract without storage \
instead of a multisig smart contract."~pp:(funppfcontract->Format.fprintfppf"Contract has no storage %a."Contract_hash.ppcontract)Data_encoding.(obj1(req"contract"Contract.originated_encoding))(functionContract_has_no_storagec->Somec|_->None)(func->Contract_has_no_storagec);register_error_kind`Permanent~id:"contractHasUnexpectedStorage"~title:"The storage of the given contract is not of the shape expected for a \
multisig contract"~description:"A multisig command has referenced a smart contract whose storage is of \
a different shape than the expected one."~pp:(funppfcontract->Format.fprintfppf"Contract has unexpected storage %a."Contract_hash.ppcontract)Data_encoding.(obj1(req"contract"Contract.originated_encoding))(functionContract_has_unexpected_storagec->Somec|_->None)(func->Contract_has_unexpected_storagec);register_error_kind`Permanent~id:"invalidSignature"~title:"The following signature did not match a public key in the given \
multisig contract"~description:"A signature was given for a multisig contract that matched none of the \
public keys of the contract signers"~pp:(funppfs->Format.fprintfppf"Invalid signature %s."(Signature.to_b58checks))Data_encoding.(obj1(req"invalid_signature"Signature.encoding))(functionInvalid_signatures->Somes|_->None)(funs->Invalid_signatures);register_error_kind`Permanent~id:"notEnoughSignatures"~title:"Not enough signatures were provided for this multisig action"~description:"To run an action on a multisig contract, you should provide at least as \
many signatures as indicated by the threshold stored in the multisig \
contract."~pp:(funppf(threshold,nsigs)->Format.fprintfppf"Not enough signatures: only %d signatures were given but the \
threshold is currently %d"nsigsthreshold)Data_encoding.(obj1(req"threshold_nsigs"(tup2int31int31)))(function|Not_enough_signatures(threshold,nsigs)->Some(threshold,nsigs)|_->None)(fun(threshold,nsigs)->Not_enough_signatures(threshold,nsigs));register_error_kind`Permanent~id:"actionDeserialisation"~title:"The expression is not a valid multisig action"~description:"When trying to deserialise an action from a sequence of bytes, we got \
an expression that does not correspond to a known multisig action"~pp:(funppfe->Format.fprintfppf"Action deserialisation error %a."Michelson_v1_printer.print_expre)Data_encoding.(obj1(req"expr"Script.expr_encoding))(functionAction_deserialisation_errore->Somee|_->None)(fune->Action_deserialisation_errore);register_error_kind`Permanent~id:"bytesDeserialisation"~title:"The byte sequence is not a valid multisig action"~description:"When trying to deserialise an action from a sequence of bytes, we got \
an error"~pp:(funppfb->Format.fprintfppf"Bytes deserialisation error %s."(Bytes.to_stringb))Data_encoding.(obj1(req"expr"bytes))(functionBytes_deserialisation_errorb->Someb|_->None)(funb->Bytes_deserialisation_errorb);register_error_kind`Permanent~id:"badDeserializedContract"~title:"The byte sequence is not for the given multisig contract"~description:"When trying to deserialise an action from a sequence of bytes, we got \
an action for another multisig contract"~pp:(funppf(received,expected)->Format.fprintfppf"Bad deserialized contract, received %a expected %a."Contract_hash.ppreceivedContract_hash.ppexpected)Data_encoding.(obj1(req"received_expected"(tup2Contract.originated_encodingContract.originated_encoding)))(functionBad_deserialized_contractb->Someb|_->None)(funb->Bad_deserialized_contractb);register_error_kind`Permanent~id:"Bad deserialized counter"~title:"Deserialized counter does not match the stored one"~description:"The byte sequence references a multisig counter that does not match the \
one currently stored in the given multisig contract"~pp:(funppf(received,expected)->Format.fprintfppf"Bad deserialized counter, received %d expected %d."receivedexpected)Data_encoding.(obj1(req"received_expected"(tup2int31int31)))(function|Bad_deserialized_counter{received;expected}->Some(Z.to_intreceived,Z.to_intexpected)|_->None)(fun(received,expected)->Bad_deserialized_counter{received=Z.of_intreceived;expected=Z.of_intexpected});register_error_kind`Permanent~id:"thresholdTooHigh"~title:"Given threshold is too high"~description:"The given threshold is higher than the number of keys, this would lead \
to a frozen multisig contract"~pp:(funppf(threshold,nkeys)->Format.fprintfppf"Threshold too high: %d expected at most %d."thresholdnkeys)Data_encoding.(obj1(req"received_expected"(tup2int31int31)))(functionThreshold_too_high(c1,c2)->Some(c1,c2)|_->None)(fun(c1,c2)->Threshold_too_high(c1,c2));register_error_kind`Permanent~id:"nonPositiveThreshold"~title:"Given threshold is not positive"~description:"A multisig threshold should be a positive number"~pp:(funppfthreshold->Format.fprintfppf"Multisig threshold %d should be positive."threshold)Data_encoding.(obj1(req"threshold"int31))(functionNon_positive_thresholdt->Somet|_->None)(funt->Non_positive_thresholdt);register_error_kind`Permanent~id:"unsupportedGenericMultisigFeature"~title:"Unsupported multisig feature: generic call"~description:"This multisig contract does not feature calling contracts with arguments"~pp:(funppfarg->Format.fprintfppf"This multisig contract can only transfer tokens to contracts of type \
unit; calling a contract with argument %a is not supported."Michelson_v1_printer.print_exprarg)Data_encoding.(obj1(req"arg"Script.expr_encoding))(functionUnsupported_feature_generic_callarg->Somearg|_->None)(funarg->Unsupported_feature_generic_callarg);register_error_kind`Permanent~id:"unsupportedGenericMultisigFeatureTy"~title:"Unsupported multisig feature: generic call to non-unit entrypoint"~description:"This multisig contract does not feature calling contracts with arguments"~pp:(funppfty->Format.fprintfppf"This multisig contract can only transfer tokens to contracts of type \
unit; calling a contract of type %a is not supported."Michelson_v1_printer.print_exprty)Data_encoding.(obj1(req"ty"Script.expr_encoding))(functionUnsupported_feature_generic_call_tyty->Somety|_->None)(funty->Unsupported_feature_generic_call_tyty);register_error_kind`Permanent~id:"unsupportedGenericMultisigLambda"~title:"Unsupported multisig feature: running lambda"~description:"This multisig contract does not feature running lambdas"~pp:(funppflam->Format.fprintfppf"This multisig contract has a fixed set of actions, it cannot run the \
following lambda: %s."lam)Data_encoding.(obj1(req"lam"string))(functionUnsupported_feature_lambdalam->Somelam|_->None)(funlam->Unsupported_feature_lambdalam);register_error_kind`Permanent~id:"illTypedArgumentForMultisig"~title:"Ill-typed argument in multi-signed transfer"~description:"The provided argument for a transfer from a multisig contract is \
ill-typed"~pp:(funppf(destination,entrypoint,parameter_ty,parameter)->Format.fprintfppf"The entrypoint %a of contract %a called from a multisig contract is \
of type %a; the provided parameter %a is ill-typed."Entrypoint.ppentrypointContract.ppdestinationMichelson_v1_printer.print_exprparameter_tyMichelson_v1_printer.print_exprparameter)Data_encoding.(obj4(req"destination"Contract.encoding)(req"entrypoint"Entrypoint.simple_encoding)(req"parameter_ty"Script.expr_encoding)(req"parameter"Script.expr_encoding))(function|Ill_typed_argument(destination,entrypoint,parameter_ty,parameter)->Some(destination,entrypoint,parameter_ty,parameter)|_->None)(fun(destination,entrypoint,parameter_ty,parameter)->Ill_typed_argument(destination,entrypoint,parameter_ty,parameter));register_error_kind`Permanent~id:"illTypedLambdaForMultisig"~title:"Ill-typed lambda for multi-signed transfer"~description:"The provided lambda for a transfer from a multisig contract is ill-typed"~pp:(funppf(lam,exp)->Format.fprintfppf"The provided lambda %a for multisig contract is ill-typed; %a is \
expected."Michelson_v1_printer.print_exprlamMichelson_v1_printer.print_exprexp)Data_encoding.(obj2(req"lam"Script.expr_encoding)(req"exp"Script.expr_encoding))(functionIll_typed_lambda(lam,exp)->Some(lam,exp)|_->None)(fun(lam,exp)->Ill_typed_lambda(lam,exp))(* The multisig contract script written by Arthur Breitman
https://github.com/murbard/smart-contracts/blob/abdb582d8f1fe7ba7eb15975867d8862cb70acfe/multisig/michelson/generic.tz *)letmultisig_script_string={|
parameter (or (unit %default)
(pair %main
(pair :payload
(nat %counter) # counter, used to prevent replay attacks
(or :action # payload to sign, represents the requested action
(lambda %operation unit (list operation))
(pair %change_keys # change the keys controlling the multisig
(nat %threshold) # new threshold
(list %keys key)))) # new list of keys
(list %sigs (option signature)))); # signatures
storage (pair (nat %stored_counter) (pair (nat %threshold) (list %keys key))) ;
code
{
UNPAIR ;
IF_LEFT
{ # Default entry point: do nothing
# This entry point can be used to send tokens to this contract
DROP ; NIL operation ; PAIR }
{ # Main entry point
# Assert no token was sent:
# to send tokens, the default entry point should be used
PUSH mutez 0 ; AMOUNT ; ASSERT_CMPEQ ;
SWAP ; DUP ; DIP { SWAP } ;
DIP
{
UNPAIR ;
# pair the payload with the current contract address, to ensure signatures
# can't be replayed across different contracts if a key is reused.
DUP ; SELF ; ADDRESS ; CHAIN_ID ; PAIR ; PAIR ;
PACK ; # form the binary payload that we expect to be signed
DIP { UNPAIR @counter ; DIP { SWAP } } ; SWAP
} ;
# Check that the counters match
UNPAIR @stored_counter; DIP { SWAP };
ASSERT_CMPEQ ;
# Compute the number of valid signatures
DIP { SWAP } ; UNPAIR @threshold @keys;
DIP
{
# Running count of valid signatures
PUSH @valid nat 0; SWAP ;
ITER
{
DIP { SWAP } ; SWAP ;
IF_CONS
{
IF_SOME
{ SWAP ;
DIP
{
SWAP ; DIIP { DUUP } ;
# Checks signatures, fails if invalid
{ DUUUP; DIP {CHECK_SIGNATURE}; SWAP; IF {DROP} {FAILWITH} };
PUSH nat 1 ; ADD @valid } }
{ SWAP ; DROP }
}
{
# There were fewer signatures in the list
# than keys. Not all signatures must be present, but
# they should be marked as absent using the option type.
FAIL
} ;
SWAP
}
} ;
# Assert that the threshold is less than or equal to the
# number of valid signatures.
ASSERT_CMPLE ;
# Assert no unchecked signature remains
IF_CONS {FAIL} {} ;
DROP ;
# Increment counter and place in storage
DIP { UNPAIR ; PUSH nat 1 ; ADD @new_counter ; PAIR} ;
# We have now handled the signature verification part,
# produce the operation requested by the signers.
IF_LEFT
{ # Get operation
UNIT ; EXEC
}
{
# Change set of signatures
DIP { CAR } ; SWAP ; PAIR ; NIL operation
};
PAIR }
}
|}(* Client_proto_context.originate expects the contract script as a Script.expr *)letmultisig_script:Script.expr=Michelson_v1_parser.parse_toplevel?check:(Sometrue)multisig_script_string|>Tezos_micheline.Micheline_parser.no_parsing_error|>function|Error_->assertfalse(* This is a top level assertion, it is asserted when the client's process runs. *)|Okparsing_result->parsing_result.Michelson_v1_parser.expandedletmultisig_script_hash=letbytes=Data_encoding.Binary.to_bytes_exnScript.expr_encodingmultisig_scriptinScript_expr_hash.hash_bytes[bytes](* The previous multisig script is the only one that the client can
originate but the client knows how to interact with several
versions of the multisig contract. For each version, the description
indicates which features are available and how to interact with
the contract. *)typemultisig_contract_description={hash:Script_expr_hash.t;(* The hash of the contract script *)requires_chain_id:bool;(* The signatures should contain the chain identifier *)main_entrypoint:Entrypoint.toption;(* name of the main entrypoint of the multisig contract, None means use the default entrypoint *)generic:bool;(* False means that the contract uses a custom action type, true
means that the contract expects the action as a (lambda unit
(list operation)). *)}letentrypoint_main=Entrypoint.of_string_strict_exn"main"(* List of known multisig contracts hashes with their kinds *)letknown_multisig_contracts:multisig_contract_descriptionlist=[{(* First supported version of the generic multisig contract. Supports incoming
transfers from unauthenticated senders and outgoing transfers of
arbitrary operation lists.
See docs/user/multisig.rst for more details. *)hash=multisig_script_hash;requires_chain_id=true;main_entrypoint=Someentrypoint_main;generic=true;};{(* Fourth supported version of the legacy multisig contract. This script is
functionally equivalent to the third version but uses the [DUP 2]
instruction introduced in Edo instead of the macro for [DIG 2; DUP; DUG 3]. *)hash=Script_expr_hash.of_b58check_exn"exprutz4BVGJ3Qms6qjmqvUF8sEk27H1cfqhRT17qpTdhEs5hEjbWm";requires_chain_id=true;main_entrypoint=None;generic=false;};{(* Third supported version of the legacy multisig contract. This script is
functionally equivalent to the second version but uses the [DIP 2]
instruction introduced in Babylon instead of the [DIIP] macro. *)hash=Script_expr_hash.of_b58check_exn"exprumpS39YZd26Cn4kyKUK5ezTR3at838iGWg7i6uETv8enDeAnfb";requires_chain_id=true;main_entrypoint=None;generic=false;};{(* Second supported version of the legacy multisig contract. This script
is the one resulting from the stitching of the Babylon protocol, the
only difference with the first version is that the chain id is part of
the data to sign. *)hash=Script_expr_hash.of_b58check_exn"exprtw1v4KvQN414oEXdGuA1U3eQizuCdS8cipx8QGK8TbNLRwc3qL";requires_chain_id=true;main_entrypoint=None;generic=false;};{(* First supported version of the legacy multisig contract. This script should not
be used anymore because it is subject to a small replay attack: when
the test chain is forked both instances have the same address and
counter so whatever happens on the test chain can be replayed on the
main chain. The script has been fixed during the activation of the
Babylon protocol. *)hash=Script_expr_hash.of_b58check_exn"expru4Ju9kf6MQ216FxUEsb9P6j8UhkPtsFcYP8r9XhQSRb47FZGfM";requires_chain_id=false;main_entrypoint=None;generic=false;};]letknown_multisig_hashes=List.map(fundescr->descr.hash)known_multisig_contractsletcheck_multisig_script_hashhash:multisig_contract_descriptiontzresultLwt.t=letopenLwt_result_syntaxinmatchList.find_opt(fund->Script_expr_hash.(d.hash=hash))known_multisig_contractswith|None->tzfail(Not_a_supported_multisig_contracthash)|Somed->returnd(* Returns [Ok ()] if [~contract] is an originated contract whose code
is [multisig_script] *)letcheck_multisig_contract(cctxt:#Protocol_client_context.full)~chain~blockcontract=letopenLwt_result_syntaxinlet*hash_opt=Client_proto_context.get_script_hashcctxt~chain~blockcontractinmatchhash_optwith|None->tzfail(Contract_has_no_scriptcontract)|Somehash->check_multisig_script_hashhash(* Some Michelson building functions, specific to the needs of the multisig
interface.*)(* The type of the lambdas consumed by the generic script *)letlambda_action_t~loc=lambda_t~loc(unit_t~loc)(operations_t~loc)(* Conversion functions from common types to Script_expr using the optimized representation *)letmutez~loc(amount:Tez.t)=int~loc(Z.of_int64(Tez.to_mutezamount))letoptimized_key_hash~loc(key_hash:Signature.Public_key_hash.t)=bytes~loc(Data_encoding.Binary.to_bytes_exnSignature.Public_key_hash.encodingkey_hash)letoptimized_address~loc~(address:Contract.t)~(entrypoint:Entrypoint.t)=bytes~loc(Data_encoding.Binary.to_bytes_exnData_encoding.(tup2Contract.encodingEntrypoint.value_encoding)(address,entrypoint))letoptimized_key~loc(key:Signature.Public_key.t)=bytes~loc(Data_encoding.Binary.to_bytes_exnSignature.Public_key.encodingkey)(** * Actions *)typemultisig_action=|Transferof{amount:Tez.t;destination:Contract.t;entrypoint:Entrypoint.t;parameter_type:Script.expr;parameter:Script.expr;}|Change_delegateofpublic_key_hashoption|LambdaofScript.expr|Change_keysofZ.t*public_keylistletaction_to_expr_generic~loc=letopenResult_syntaxinfunction|Transfer{amount;destination;entrypoint;parameter_type;parameter}->(matchdestinationwith|Implicitdestination->let*a=lambda_from_string@@Managed_contract.build_lambda_for_transfer_to_implicit~destination~amountinreturn@@left~loca|Originateddestination->let*a=lambda_from_string@@Managed_contract.build_lambda_for_transfer_to_originated~destination~entrypoint~parameter_type~parameter~amountinreturn@@left~loca)|Lambdacode->returnTezos_micheline.Micheline.(left~loc(rootcode))|Change_delegatedelegate->let*a=lambda_from_string@@Managed_contract.build_lambda_for_set_delegate~delegateinreturn@@left~loca|Change_keys(threshold,keys)->letoptimized_keys=seq~loc(List.map(optimized_key~loc)keys)inletexpr=right~loc(pair~loc(int~locthreshold)optimized_keys)inreturnexprletaction_to_expr_legacy~loc=letopenResult_syntaxinfunction|Transfer{amount;destination;entrypoint;parameter_type;parameter}->ifparameter<>Tezos_micheline.Micheline.strip_locations(unit~loc:())thentzfail@@Unsupported_feature_generic_callparameterelseifparameter_type<>Tezos_micheline.Micheline.strip_locations(unit_t~loc:())thentzfail@@Unsupported_feature_generic_call_typarameter_typeelsereturn@@left~loc(pair~loc(mutez~locamount)(optimized_address~loc~address:destination~entrypoint))|Lambda_->tzfail@@Unsupported_feature_lambda""|Change_delegatedelegate->letdelegate_opt=matchdelegatewith|None->none~loc()|Somedelegate->some~loc(optimized_key_hash~locdelegate)inreturn@@right~loc(left~locdelegate_opt)|Change_keys(threshold,keys)->letoptimized_keys=seq~loc(List.map(optimized_key~loc)keys)inletexpr=right~loc(pair~loc(int~locthreshold)optimized_keys)inreturn(right~locexpr)letaction_to_expr~loc~genericaction=ifgenericthenaction_to_expr_generic~locactionelseaction_to_expr_legacy~locactionletaction_of_expr_generice=letopenLwt_result_syntaxinletfail()=tzfail(Action_deserialisation_error(Tezos_micheline.Micheline.strip_locationse))inmatchewith|Tezos_micheline.Micheline.Prim(_,Script.D_Left,[lam],[])->return(Lambda(Tezos_micheline.Micheline.strip_locationslam))|Tezos_micheline.Micheline.Prim(_,Script.D_Right,[Tezos_micheline.Micheline.Prim(_,Script.D_Pair,[Tezos_micheline.Micheline.Int(_,threshold);Tezos_micheline.Micheline.Seq(_,key_bytes);],[]);],[])->let*keys=List.map_es(function|Tezos_micheline.Micheline.Bytes(_,s)->return@@Data_encoding.Binary.of_bytes_exnSignature.Public_key.encodings|_->fail())key_bytesinreturn(Change_keys(threshold,keys))|_->fail()letaction_of_expr_not_generice=letopenLwt_result_syntaxinletfail()=tzfail(Action_deserialisation_error(Tezos_micheline.Micheline.strip_locationse))inmatchewith|Tezos_micheline.Micheline.Prim(_,Script.D_Left,[Tezos_micheline.Micheline.Prim(_,Script.D_Pair,[Tezos_micheline.Micheline.Int(_,i);Tezos_micheline.Micheline.Bytes(_,s);],[]);],[])->(matchTez.of_mutez(Z.to_int64i)with|None->fail()|Someamount->return@@Transfer{amount;destination=Data_encoding.Binary.of_bytes_exnContract.encodings;entrypoint=Entrypoint.default;parameter_type=Tezos_micheline.Micheline.strip_locations@@unit_t~loc:();parameter=Tezos_micheline.Micheline.strip_locations@@unit~loc:();})|Tezos_micheline.Micheline.Prim(_,Script.D_Right,[Tezos_micheline.Micheline.Prim(_,Script.D_Left,[Tezos_micheline.Micheline.Prim(_,Script.D_None,[],[])],[]);],[])->return(Change_delegateNone)|Tezos_micheline.Micheline.Prim(_,Script.D_Right,[Tezos_micheline.Micheline.Prim(_,Script.D_Left,[Tezos_micheline.Micheline.Prim(_,Script.D_Some,[Tezos_micheline.Micheline.Bytes(_,s)],[]);],[]);],[])->return@@Change_delegate(Some(Data_encoding.Binary.of_bytes_exnSignature.Public_key_hash.encodings))|Tezos_micheline.Micheline.Prim(_,Script.D_Right,[Tezos_micheline.Micheline.Prim(_,Script.D_Right,[Tezos_micheline.Micheline.Prim(_,Script.D_Pair,[Tezos_micheline.Micheline.Int(_,threshold);Tezos_micheline.Micheline.Seq(_,key_bytes);],[]);],[]);],[])->let*keys=List.map_es(function|Tezos_micheline.Micheline.Bytes(_,s)->return@@Data_encoding.Binary.of_bytes_exnSignature.Public_key.encodings|_->fail())key_bytesinreturn(Change_keys(threshold,keys))|_->fail()letaction_of_expr~generic=ifgenericthenaction_of_expr_genericelseaction_of_expr_not_generictypekey_list=Signature.Public_key.tlist(* The relevant information that we can get about a multisig smart contract *)typemultisig_contract_information={counter:Z.t;threshold:Z.t;keys:key_list;}letmultisig_get_information(cctxt:#Protocol_client_context.full)~chain~blockcontract=letopenClient_proto_contextinletopenTezos_micheline.MichelineinletopenLwt_result_syntaxinlet*storage_opt=get_storagecctxt~chain~block~unparsing_mode:Readablecontractinmatchstorage_optwith|None->tzfail(Contract_has_no_storagecontract)|Somestorage->(matchrootstoragewith|Prim(_,D_Pair,[Int(_,counter);Int(_,threshold);Seq(_,key_nodes)],_)->let*keys=List.map_es(function|String(_,key_str)->return@@Signature.Public_key.of_b58check_exnkey_str|_->tzfail(Contract_has_unexpected_storagecontract))key_nodesinreturn{counter;threshold;keys}|_->tzfail(Contract_has_unexpected_storagecontract))letmultisig_create_storage~counter~threshold~keys():Script.exprtzresultLwt.t=letopenTezos_micheline.MichelineinletopenLwt_result_syntaxinletloc=Tezos_micheline.Micheline_parser.location_zeroinlet*l=List.map_es(funkey->letkey_str=Signature.Public_key.to_b58checkkeyinreturn(String(loc,key_str)))keysinreturn@@strip_locations@@pair~loc(int~loccounter)(pair~loc(int~locthreshold)(seq~locl))(* Client_proto_context.originate expects the initial storage as a string *)letmultisig_storage_string~counter~threshold~keys()=letopenLwt_result_syntaxinlet*expr=multisig_create_storage~counter~threshold~keys()inreturn@@Format.asprintf"%a"Michelson_v1_printer.print_exprexprletmultisig_create_param~counter~generic~action~optional_signatures():Script.exprtzresultLwt.t=letopenTezos_micheline.MichelineinletopenLwt_result_syntaxinletloc=0inlet*l=List.map_es(funsig_opt->matchsig_optwith|None->return@@none~loc()|Somesignature->return@@some~loc(String(loc,Signature.to_b58checksignature)))optional_signaturesinlet*?expr=action_to_expr~loc~genericactioninreturn@@strip_locations@@pair~loc(pair~loc(int~loccounter)expr)(Seq(loc,l))letmultisig_param~counter~action~optional_signatures~generic()=letopenLwt_result_syntaxinlet*expr=multisig_create_param~counter~action~optional_signatures~generic()inreturn@@Script.lazy_exprexprletget_contract_address_maybe_chain_id~descr~loc~chain_idcontract=letaddress=bytes~loc(Data_encoding.Binary.to_bytes_exnContract.encodingcontract)inifdescr.requires_chain_idthenletchain_id_bytes=bytes~loc(Data_encoding.Binary.to_bytes_exnChain_id.encodingchain_id)inpair~locchain_id_bytesaddresselseaddressletmultisig_bytes~counter~action~contract~chain_id~descr()=letopenLwt_result_syntaxinletloc=0inlet*?expr=action_to_expr~loc~generic:descr.genericactioninlettriple=pair~loc(get_contract_address_maybe_chain_id~descr~loc~chain_idcontract)(pair~loc(int~loccounter)expr)inletbytes=Data_encoding.Binary.to_bytes_exnScript.expr_encoding@@Tezos_micheline.Micheline.strip_locations@@tripleinreturn@@Bytes.cat(Bytes.of_string"\005")bytesletcheck_threshold~threshold~keys()=letopenLwt_result_syntaxinletthreshold=Z.to_intthresholdinifCompare.List_length_with.(keys<threshold)thentzfail(Threshold_too_high(threshold,List.lengthkeys))elseifCompare.Int.(threshold<=0)thentzfail(Non_positive_thresholdthreshold)elsereturn_unitletoriginate_multisig(cctxt:#Protocol_client_context.full)~chain~block?confirmations?dry_run?branch?fee?gas_limit?storage_limit?verbose_signing~delegate~threshold~keys~balance~source~src_pk~src_sk~fee_parameter()=letopenLwt_result_syntaxinlet*initial_storage=multisig_storage_string~counter:Z.zero~threshold~keys()inlet*()=check_threshold~threshold~keys()inClient_proto_context.originate_contractcctxt~chain~block?branch?confirmations?dry_run?fee?gas_limit?storage_limit?verbose_signing~delegate~initial_storage~balance~source~src_pk~src_sk~code:multisig_script~fee_parameter()typemultisig_prepared_action={bytes:Bytes.t;threshold:Z.t;keys:public_keylist;counter:Z.t;entrypoint:Entrypoint.toption;generic:bool;}letcheck_parameter_type(cctxt:#Protocol_client_context.full)?gas?legacy~destination~entrypoint~parameter_type~parameter()=letopenLwt_result_syntaxinlet*_=trace(Ill_typed_argument(destination,entrypoint,parameter_type,parameter))@@Plugin.RPC.Scripts.typecheck_datacctxt(cctxt#chain,cctxt#block)~data:parameter~ty:parameter_type?gas?legacyinreturn_unitletcheck_action(cctxt:#Protocol_client_context.full)~action~balance?gas?legacy()=letopenLwt_result_syntaxinmatchactionwith|Change_keys(threshold,keys)->let*()=check_threshold~threshold~keys()inreturn_unit|Transfer{amount;destination;entrypoint;parameter_type;parameter}->let*()=check_parameter_typecctxt~destination~entrypoint~parameter_type~parameter()inifTez.(amount>balance)then(* This is warning only because the contract can be filled
before sending the signatures or even in the same
transaction *)Format.eprintf"Transferred amount is bigger than current multisig balance";return_unit|Lambdacode->letaction_t=Tezos_micheline.Micheline.strip_locations(lambda_action_t~loc:())inlet*_remaining_gas=trace(Ill_typed_lambda(code,action_t))@@Plugin.RPC.Scripts.typecheck_datacctxt(cctxt#chain,cctxt#block)~data:code~ty:action_t?gas?legacyinreturn_unit|_->return_unitletprepare_multisig_transaction(cctxt:#Protocol_client_context.full)~chain~block~multisig_contract~action()=letopenLwt_result_syntaxinletcontract=multisig_contractinlet*descr=check_multisig_contractcctxt~chain~blockcontractinlet*{counter;threshold;keys}=multisig_get_informationcctxt~chain~blockcontractinlet*chain_id=Chain_services.chain_idcctxt~chain()inletcontract=Contract.Originatedcontractinlet*bytes=multisig_bytes~counter~action~contract~descr~chain_id()inlet*balance=Client_proto_context.get_balancecctxt~chain:cctxt#chain~block:cctxt#blockcontractinlet*()=check_actioncctxt~action~balance()inreturn{bytes;threshold;keys;counter;entrypoint=descr.main_entrypoint;generic=descr.generic;}letcheck_multisig_signatures~bytes~threshold~keyssignatures=letopenLwt_result_syntaxinletkey_array=Array.of_listkeysinletnkeys=Array.lengthkey_arrayinletopt_sigs_arr=Array.makenkeysNoneinletmatching_key_found=reffalseinletcheck_signature_against_key_numbersignatureikey=ifSignature.checkkeysignaturebytesthen(matching_key_found:=true;opt_sigs_arr.(i)<-Somesignature)inlet*()=List.iter_ep(funsignature->matching_key_found:=false;List.iteri(check_signature_against_key_numbersignature)keys;fail_unless!matching_key_found(Invalid_signaturesignature))signaturesinletopt_sigs=Array.to_listopt_sigs_arrinletsignature_count=List.fold_left(funnsig_opt->matchsig_optwithSome_->n+1|None->n)0opt_sigsinletthreshold_int=Z.to_intthresholdinifsignature_count>=threshold_intthenreturnopt_sigselsetzfail(Not_enough_signatures(threshold_int,signature_count))letcall_multisig(cctxt:#Protocol_client_context.full)~chain~block?confirmations?dry_run?verbose_signing?branch~source~src_pk~src_sk~multisig_contract~action~signatures~amount?fee?gas_limit?storage_limit?counter~fee_parameter()=letopenLwt_result_syntaxinlet*{bytes;threshold;keys;counter=stored_counter;entrypoint;generic}=prepare_multisig_transactioncctxt~chain~block~multisig_contract~action()inlet*optional_signatures=check_multisig_signatures~bytes~threshold~keyssignaturesinlet*parameters=multisig_param~counter:stored_counter~action~optional_signatures~generic()inClient_proto_context.transfer_with_scriptcctxt~chain~block?confirmations?dry_run?branch~source~src_pk~src_sk~destination:(Originatedmultisig_contract)?entrypoint~parameters~amount?fee?gas_limit?storage_limit?counter~fee_parameter?verbose_signing()letaction_of_bytes~multisig_contract~stored_counter~descr~chain_idbytes=letopenLwt_result_syntaxinifCompare.Int.(Bytes.lengthbytes>=1)&&Compare.Int.(TzEndian.get_uint8bytes0=0x05)thenletnbytes=Bytes.subbytes1(Bytes.lengthbytes-1)inmatchData_encoding.Binary.of_bytes_optScript.expr_encodingnbyteswith|None->tzfail(Bytes_deserialisation_errorbytes)|Somee->(matchTezos_micheline.Micheline.rootewith|Tezos_micheline.Micheline.Prim(_,Script.D_Pair,[Tezos_micheline.Micheline.Bytes(_,contract_bytes);Tezos_micheline.Micheline.Prim(_,Script.D_Pair,[Tezos_micheline.Micheline.Int(_,counter);e],[]);],[])whennotdescr.requires_chain_id->letcontract=Data_encoding.Binary.of_bytes_exnContract.originated_encodingcontract_bytesinifcounter=stored_counterthenifContract_hash.(multisig_contract=contract)thenaction_of_expr~generic:descr.genericeelsetzfail(Bad_deserialized_contract(contract,multisig_contract))elsetzfail(Bad_deserialized_counter{received=counter;expected=stored_counter})|Tezos_micheline.Micheline.Prim(_,Script.D_Pair,[Tezos_micheline.Micheline.Prim(_,Script.D_Pair,[Tezos_micheline.Micheline.Bytes(_,chain_id_bytes);Tezos_micheline.Micheline.Bytes(_,contract_bytes);],[]);Tezos_micheline.Micheline.Prim(_,Script.D_Pair,[Tezos_micheline.Micheline.Int(_,counter);e],[]);],[])whendescr.requires_chain_id->letcontract=Data_encoding.Binary.of_bytes_exnContract.originated_encodingcontract_bytesinletcid=Data_encoding.Binary.of_bytes_exnChain_id.encodingchain_id_bytesinifcounter=stored_counterthenifmultisig_contract=contract&&chain_id=cidthenaction_of_expr~generic:descr.genericeelsetzfail(Bad_deserialized_contract(contract,multisig_contract))elsetzfail(Bad_deserialized_counter{received=counter;expected=stored_counter})|_->tzfail(Bytes_deserialisation_errorbytes))elsetzfail(Bytes_deserialisation_errorbytes)letcall_multisig_on_bytes(cctxt:#Protocol_client_context.full)~chain~block?confirmations?dry_run?verbose_signing?branch~source~src_pk~src_sk~multisig_contract~bytes~signatures~amount?fee?gas_limit?storage_limit?counter~fee_parameter()=letopenLwt_result_syntaxinlet*info=multisig_get_informationcctxt~chain~blockmultisig_contractinlet*descr=check_multisig_contractcctxt~chain~blockmultisig_contractinlet*chain_id=Chain_services.chain_idcctxt~chain()inlet*action=action_of_bytes~multisig_contract~stored_counter:info.counter~chain_id~descrbytesincall_multisigcctxt~chain~block?confirmations?dry_run?branch~source~src_pk~src_sk~multisig_contract~action~signatures~amount?fee?gas_limit?storage_limit?counter~fee_parameter?verbose_signing()