123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582(*****************************************************************************)(* *)(* Open Source License *)(* Copyright (c) 2020 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_contextopenTezos_michelinetypeerror+=Contract_has_no_scriptofContract.ttypeerror+=Contract_has_no_storageofContract.ttypeerror+=Entrypoint_mismatchofstring*(Script.expr*Script.expr)optiontypeerror+=Action_unwrapping_errorofstring*Script.exprtypeerror+=Not_an_entrypointofScript.exprletentrypoint_mismatch_explanationppf(name,ty)=matchtywith|None->Format.fprintfppf"Entrypoint %s is missing"name|Some(ty,expected)->Format.fprintfppf"Entrypoint \"%s\" has type @[%a@], but should have type @[%a@]"nameMichelson_v1_printer.print_exprtyMichelson_v1_printer.print_exprexpectedlet()=register_error_kind`Permanent~id:"fa12ContractHasNoScript"~title:"The given contract is not a smart contract"~description:"An FA1.2 command has referenced a scriptless contract."~pp:(funppfcontract->Format.fprintfppf"Contract %a is not a smart contract, it has no script."Contract.ppcontract)Data_encoding.(obj1(req"contract"Contract.encoding))(functionContract_has_no_scriptc->Somec|_->None)(func->Contract_has_no_scriptc);register_error_kind`Permanent~id:"fa12ContractHasNoStorage"~title:"The given contract has no storage"~description:"An FA1.2 command made a call on a contract that has no storage."~pp:(funppfcontract->Format.fprintfppf"Contract %a has no storage."Contract.ppcontract)Data_encoding.(obj1(req"contract"Contract.encoding))(functionContract_has_no_storagec->Somec|_->None)(func->Contract_has_no_storagec);register_error_kind`Permanent~id:"entrypointMismatch"~title:"The given contract does not implement the FA1.2 interface"~description:"An FA1.2 command has referenced a smart contract whose script does not \
implement at least one FA1.2 entrypoint, or with an incompatible type. \
See TZIP-7 \
(https://gitlab.com/tzip/tzip/-/blob/master/proposals/tzip-7/tzip-7.md) \
for documentation on FA1.2."~pp:(funppf(name,ty)->Format.fprintfppf"Not a supported FA1.2 contract.@\n%a."entrypoint_mismatch_explanation(name,ty))Data_encoding.(obj2(req"name"string)(req"type"(option(tup2Script.expr_encodingScript.expr_encoding))))(functionEntrypoint_mismatch(n,t)->Some(n,t)|_->None)(fun(n,t)->Entrypoint_mismatch(n,t));register_error_kind`Permanent~id:"actionUnwrappingError"~title:"The argument is not for an FA1.2 parameter"~description:"The argument's type does not correspond to that of the corresponding \
FA1.2 entrypoint."~pp:(funppf(entrypoint,expr)->Format.fprintfppf"Not a supported FA1.2 entrypoint argument.@\nEntrypoint: %s@\n%a."entrypointMichelson_v1_printer.print_exprexpr)Data_encoding.(obj2(req"entrypoint"string)(req"expr"Script.expr_encoding))(functionAction_unwrapping_error(s,e)->Some(s,e)|_->None)(fun(s,e)->Action_unwrapping_error(s,e));register_error_kind`Permanent~id:"notAnEntrypoint"~title:"The expression is not for an entrypoint"~description:"The parameter value of the contract call refers to a non-existing \
entrypoint."~pp:(funppfparam->Format.fprintfppf"Not a parameter for an entrypoint.@\n%a."Michelson_v1_printer.print_exprparam)Data_encoding.(obj1(req"param"Script.expr_encoding))(functionNot_an_entrypointe->Somee|_->None)(fune->Not_an_entrypointe)letcallback_encoding=Data_encoding.(conv(fun(c,e)->(c,Option.value~default:""e))(fun(c,e)->(c,ifString.equale""thenNoneelseSomee))(tup2Contract.encodingVariable.string))(** Michelson combinators *)letpair~locab=Micheline.Prim(loc,Script.D_Pair,[a;b],[])letnat~loci=Micheline.Int(loc,i)letunit~loc()=Micheline.Prim(loc,Script.D_Unit,[],[])letbytes~locb=Micheline.Bytes(loc,b)letaddress~locaddr=bytes~loc(Data_encoding.Binary.to_bytes_exnContract.encodingaddr)letcallback~loc?entrypointaddr=bytes~loc(Data_encoding.Binary.to_bytes_exncallback_encoding(addr,entrypoint))(** Types *)(** Michelson type combinators: produce a Michelson node of the
expected type, and a function to check another node is
syntactically equivalent. *)typenode=(Micheline.canonical_location,Michelson_v1_primitives.prim)Micheline.nodetypetype_eq_combinator=node*(node->bool)(** [t_pair ~loc a b] takes two types and their respective equivalence
check functions, and returns a type of pair of such types and a
function checking syntactical equivalence with another node. *)lett_pair?(loc=0)(a,is_a)(b,is_b)=letis_pairp=matchpwith|Micheline.Prim(_,Script.T_pair,[a;b],_)->is_aa&&is_bb|_->falsein(Micheline.Prim(loc,Script.T_pair,[a;b],[]),is_pair)(** [t_unit ~loc ()] returns a Micheline node for the `unit` type, and
a function checking another node is syntactically equivalent. *)lett_unit?(loc=0)():type_eq_combinator=letis_unitp=matchpwithMicheline.Prim(_,Script.T_unit,[],_)->true|_->falsein(Micheline.Prim(loc,Script.T_unit,[],[]),is_unit)(** [t_nat ~loc ()] returns a Micheline node for the `nat` type, and
a function checking another node is syntactically equivalent. *)lett_nat?(loc=0)():type_eq_combinator=letis_natp=matchpwithMicheline.Prim(_,Script.T_nat,[],_)->true|_->falsein(Micheline.Prim(loc,Script.T_nat,[],[]),is_nat)(** [t_address ~loc ()] returns a Micheline node for the `address`
type, and a function checking another node is syntactically
equivalent. *)lett_address?(loc=0)():type_eq_combinator=letis_addressp=matchpwith|Micheline.Prim(_,Script.T_address,[],_)->true|_->falsein(Micheline.Prim(loc,Script.T_address,[],[]),is_address)(** [t_contract ~loc (c, is_c)] takes a node representing a Michelson
type and its own syntactical equivalence checker, and returns a
Micheline node for the type `contract c`, and a function checking
another node is syntactically equivalent. *)lett_contract?(loc=0)(a,is_a):type_eq_combinator=letis_contractc=matchcwith|Micheline.Prim(_,Script.T_contract,[a],_)->is_aa|_->falsein(Micheline.Prim(loc,Script.T_contract,[a],[]),is_contract)(** [t_view ~loc a b] takes two node [a] and [b] and their syntactical
equivalence checking functions, and returns a Micheline node for
the `view a b` type, and a function checking another node is
syntactically equivalent. The view type is defined by
[TZIP4](https://gitlab.com/tzip/tzip/-/blob/master/proposals/tzip-4/tzip-4.md).
*)lett_view?locab:type_eq_combinator=t_pair?loca(t_contract?locb)(** * Actions *)(** Corresponds to
[TZIP7](https://gitlab.com/tzip/tzip/-/blob/master/proposals/tzip-7/tzip-7.md)
entrypoints. *)(** A callback from a view can be on a specific entrypoint of the
contract, or the default one if not specified. *)typecallback_contract=Contract.t*stringoptiontypeaction=|TransferofContract.t*Contract.t*Z.t|ApproveofContract.t*Z.t|Get_allowanceofContract.t*Contract.t*callback_contract|Get_balanceofContract.t*callback_contract|Get_total_supplyofcallback_contractletprint_callback_contractppf(c,etp)=Format.fprintfppf"%a%s"Contract.ppc(matchetpwithNone|Some""->""|Someetp->"%"^etp)letprint_actionppf=function|Transfer(src,dst,amount)->Format.fprintfppf"Transfer (%a, %a, %a)"Contract.ppsrcContract.ppdstZ.pp_printamount|Approve(addr,amount)->Format.fprintfppf"Approve (%a, %a)"Contract.ppaddrZ.pp_printamount|Get_allowance(src,dst,callback)->Format.fprintfppf"Get_allowance (%a, %a, %a)"Contract.ppsrcContract.ppdstprint_callback_contractcallback|Get_balance(addr,callback)->Format.fprintfppf"Get_balance (%a, %a)"Contract.ppaddrprint_callback_contractcallback|Get_total_supplycallback->Format.fprintfppf"Get_total_supply (%a)"print_callback_contractcallbacklettransfer_encoding=Data_encoding.(case~title:"transfer"(Tag0)(obj3(req"transfer_source"Contract.encoding)(req"transfer_destination"Contract.encoding)(req"transfer_amount"n))(function|Transfer(src,dst,amount)->Some(src,dst,amount)|_->None)(fun(src,dst,amount)->Transfer(src,dst,amount)))letapprove_encoding=Data_encoding.(case~title:"approve"(Tag1)(obj2(req"approve_address"Contract.encoding)(req"approve_amount"n))(functionApprove(addr,amount)->Some(addr,amount)|_->None)(fun(addr,amount)->Approve(addr,amount)))letgetBalance_encoding=Data_encoding.(case~title:"getBalance"(Tag2)(obj2(req"getBalance_address"Contract.encoding)(req"getBalance_callback"callback_encoding))(function|Get_balance(addr,callback)->Some(addr,callback)|_->None)(fun(addr,callback)->Get_balance(addr,callback)))letgetAllowance_encoding=Data_encoding.(case~title:"getAllowance"(Tag3)(obj3(req"getAllowance_source"Contract.encoding)(req"getAllowance_destination"Contract.encoding)(req"getAllowance_callback"callback_encoding))(function|Get_allowance(src,dst,callback)->Some(src,dst,callback)|_->None)(fun(src,dst,callback)->Get_allowance(src,dst,callback)))letgetTotalSupply_encoding=Data_encoding.(case~title:"getTotalSupply"(Tag4)(obj1(req"getTotalSupply_callback"callback_encoding))(functionGet_total_supplycallback->Somecallback|_->None)(funcallback->Get_total_supplycallback))letaction_encoding=Data_encoding.union[transfer_encoding;approve_encoding;getBalance_encoding;getAllowance_encoding;getTotalSupply_encoding;]lettransfer_type=t_pair(t_address())(t_pair(t_address())(t_nat()))letapprove_type=t_pair(t_address())(t_nat())letgetAllowance_type=t_view(t_pair(t_address())(t_address()))(t_nat())letgetBalance_type=t_view(t_address())(t_nat())letgetTotalSupply_type=t_view(t_unit())(t_nat())letstandard_entrypoints=[("transfer",transfer_type);("approve",approve_type);("getAllowance",getAllowance_type);("getBalance",getBalance_type);("getTotalSupply",getTotalSupply_type);]letaction_to_expr?(loc=0)action=matchactionwith|Transfer(source,destination,amount)->pair~loc(address~locsource)(pair~loc(address~locdestination)(nat~locamount))|Approve(addr,amount)->pair~loc(address~locaddr)(nat~locamount)|Get_allowance(source,destination,(cb,entrypoint))->pair~loc(pair~loc(address~locsource)(address~locdestination))(callback~loc?entrypointcb)|Get_balance(addr,(cb,entrypoint))->pair~loc(address~locaddr)(callback~loc?entrypointcb)|Get_total_supply(cb,entrypoint)->pair~loc(unit~loc())(callback~loc?entrypointcb)letparse_addresserror=function|Micheline.Bytes(_,b)->ok@@Data_encoding.Binary.of_bytes_exnContract.encodingb|String(_,s)->(matchContract.of_b58checkswithOkc->okc|Error_->error())|_->error()letparse_callbackerrorexpr=letof_b58_check(c,entrypoint)=matchContract.of_b58checkcwith|Okc->ok(c,entrypoint)|Error_->error()inmatchexprwith|Micheline.Bytes(_,b)->(matchData_encoding.Binary.of_bytescallback_encodingbwith|Ok(c,entrypoint)->ok(c,entrypoint)|Error_->error())|String(_,s)->(matchString.index_opts'%'with|None->of_b58_check(s,None)|Somepos->(letlen=String.lengths-pos-1inletname=String.subs(pos+1)leninmatch(String.subs0pos,name)with|addr,"default"->of_b58_check(addr,None)|addr,name->of_b58_check(addr,Somename)))|_->error()letaction_of_expr~entrypointexpr=letopenMichelineinleterror()=error(Action_unwrapping_error(entrypoint,Micheline.strip_locationsexpr))inmatch(entrypoint,expr)with(* Transfer operation before comb pairs. *)|("transfer",Prim(_,Script.D_Pair,[((Bytes(_,_)|String(_,_))assource);Prim(_,Script.D_Pair,[((Bytes(_,_)|String(_,_))asdestination);Int(_,amount);],_);],_))->parse_addresserrorsource>>?funsource->parse_addresserrordestination>>?fundestination->ok(Transfer(source,destination,amount))|("approve",Prim(_,Script.D_Pair,[((Bytes(_,_)|String(_,_))asaddr);Int(_,amount)],_))->parse_addresserroraddr>>?funaddr->ok(Approve(addr,amount))|("getBalance",Prim(_,Script.D_Pair,[((Bytes(_,_)|String(_,_))asaddr);((Bytes(_,_)|String(_,_))ascb);],_))->parse_addresserroraddr>>?funaddr->parse_callbackerrorcb>>?funcallback->ok(Get_balance(addr,callback))|("getAllowance",Prim(_,Script.D_Pair,[Prim(_,Script.D_Pair,[((Bytes(_,_)|String(_,_))assource);((Bytes(_,_)|String(_,_))asdestination);],_);((Bytes(_,_)|String(_,_))ascontract);],_))->parse_addresserrorsource>>?funsource->parse_addresserrordestination>>?fundestination->parse_callbackerrorcontract>>?funcallback->ok(Get_allowance(source,destination,callback))|("getTotalSupply",Prim(_,Script.D_Pair,[Prim(_,Script.D_Unit,[],_);((Bytes(_,_)|String(_,_))ascontract);],_))->parse_callbackerrorcontract>>?funcallback->ok(Get_total_supplycallback)|_->error()letfind_entrypoint_in_annoterrorannotsexpr=matchList.find_opt(funannot->annot.[0]='%')annotswith|Someentrypoint->action_of_expr~entrypoint:(String.subentrypoint1(String.lengthentrypoint-1))expr|None->error()letderive_actionexprt_param=leterror()=error(Not_an_entrypoint(Micheline.strip_locationsexpr))inletrecderiveexprt_param=match(expr,t_param)with|(Micheline.Prim(_,Script.D_Left,[left],_),Micheline.Prim(_,Script.T_or,[t_left;_],_))->deriveleftt_left|(Micheline.Prim(_,Script.D_Right,[right],_),Micheline.Prim(_,Script.T_or,[_;t_right],_))->deriverightt_right|_,Micheline.Prim(_,_,_,annots)->find_entrypoint_in_annoterrorannotsexpr|_->error()inderiveexprt_paramletextract_parametercontract=function|Micheline.Seq(_,l)->(List.filter_map(function|Micheline.Prim(_,Script.K_parameter,[param],_)->Someparam|_->None)l|>function|param::_->okparam|_->error(Contract_has_no_scriptcontract))|_->error(Contract_has_no_scriptcontract)letget_contract_parametercctxt~chain~blockcontract=Client_proto_context.get_scriptcctxt~chain~blockcontract>>=?function|None->fail(Contract_has_no_scriptcontract)|Some{code;_}->(matchScript_repr.force_decodecodewith|Error_->fail(Contract_has_no_scriptcontract)|Ok(code,_)->Lwt.return(extract_parametercontract(Micheline.rootcode)))letconvert_wrapped_parameter_into_actioncctxt~chain~blockcontractparam=get_contract_parametercctxt~chain~blockcontract>>=?funparameter->Lwt.return(derive_actionparamparameter)letcheck_entrypointentrypoints(name,(expected_ty,check))=matchList.assoc_opt~equal:String.equalnameentrypointswith|None->error(Entrypoint_mismatch(name,None))|Somety->ifnot(check(Micheline.rootty))thenerror(Entrypoint_mismatch(name,Some(ty,Micheline.strip_locationsexpected_ty)))elseOk()letcontract_has_fa12_interface:#Protocol_client_context.rpc_context->chain:Shell_services.chain->block:Shell_services.block->contract:Alpha_context.Contract.t->unit->unittzresultLwt.t=funcctxt~chain~block~contract()->matchContract.is_implicitcontractwith|Some_->fail(Contract_has_no_scriptcontract)|None->Michelson_v1_entrypoints.list_contract_entrypointscctxt~chain~block~contract>>=?funentrypoints->List.iter_e(check_entrypointentrypoints)standard_entrypoints|>Lwt.return