123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269(*****************************************************************************)(* *)(* Open Source License *)(* Copyright (c) 2021 Marigold <team@marigold.dev> *)(* *)(* 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. *)(* *)(*****************************************************************************)openMichelineopenMichelson_v1_primitives(*
See [expand] for an example.
TODO: https://gitlab.com/tezos/tezos/-/issues/1609
Move function to lib_micheline.
On our next opportunity to update the environment, we
should move this function to lib_micheline.
*)letbottom_up_fold_cpsinitial_accumulatornodeinitial_kf=letrectraverse_nodeaccunodek=faccunode@@funaccunode->matchnodewith|String_|Int_|Bytes_->kaccunode|Prim(loc,prim,args,annot)->(traverse_nodes[@ocaml.tailcall])accuargs(funaccuargs->faccu(Prim(loc,prim,args,annot))k)|Seq(loc,elts)->(traverse_nodes[@ocaml.tailcall])accuelts(funaccuelts->faccu(Seq(loc,elts))k)andtraverse_nodesaccunodesk=matchnodeswith|[]->kaccu[]|node::nodes->(traverse_node[@ocaml.tailcall])accunode(funaccunode->(traverse_nodes[@ocaml.tailcall])accunodes(funaccunodes->kaccu(node::nodes)))intraverse_nodeinitial_accumulatornodeinitial_kmoduleGas_costs=Global_constants_costsmoduleExpr_hash_map=Map.Make(Script_expr_hash)typeerror+=Expression_too_deeptypeerror+=Expression_already_registeredtypeerror+=Badly_formed_constant_expressiontypeerror+=Nonexistent_globaltypeerror+=Expression_too_largelet()=letdescription="Attempted to register an expression that, after fully expanding all \
referenced global constants, would result in too many levels of nesting."inregister_error_kind`Branch~id:"Expression_too_deep"~title:"Expression too deep"~description~pp:(funppf()->Format.fprintfppf"%s"description)Data_encoding.empty(functionExpression_too_deep->Some()|_->None)(fun()->Expression_too_deep);letdescription="Attempted to register an expression as global constant that has already \
been registered."inregister_error_kind`Branch~id:"Expression_already_registered"~title:"Expression already registered"~description~pp:(funppf()->Format.fprintfppf"%s"description)Data_encoding.empty(functionExpression_already_registered->Some()|_->None)(fun()->Expression_already_registered);letdescription="Found a badly formed constant expression. The 'constant' primitive must \
always be followed by a string of the hash of the expression it points \
to."inregister_error_kind`Branch~id:"Badly_formed_constant_expression"~title:"Badly formed constant expression"~description~pp:(funppf()->Format.fprintfppf"%s"description)Data_encoding.empty(functionBadly_formed_constant_expression->Some()|_->None)(fun()->Badly_formed_constant_expression);letdescription="No registered global was found at the given hash in storage."inregister_error_kind`Branch~id:"Nonexistent_global"~title:"Tried to look up nonexistent global"~description~pp:(funppf()->Format.fprintfppf"%s"description)Data_encoding.empty(functionNonexistent_global->Some()|_->None)(fun()->Nonexistent_global);letdescription="Encountered an expression that, after expanding all constants, is larger \
than the expression size limit."inregister_error_kind`Branch~id:"Expression_too_large"~title:"Expression too large"~description~pp:(funppf()->Format.fprintfppf"%s"description)Data_encoding.empty(functionExpression_too_large->Some()|_->None)(fun()->Expression_too_large)letgetcontexthash=Storage.Global_constants.Map.findcontexthash>>=?fun(context,value)->matchvaluewith|None->tzfailNonexistent_global|Somevalue->return(context,value)letexpr_to_address_in_contextcontextexpr=letlexpr=Script_repr.lazy_exprexprinRaw_context.consume_gascontext@@Script_repr.force_bytes_costlexpr>>?funcontext->Script_repr.force_byteslexpr>>?funb->Raw_context.consume_gascontext@@Gas_costs.expr_to_address_in_context_costb>|?funcontext->(context,Script_expr_hash.hash_bytes[b])letnode_too_largenode=letnode_size=Script_repr.Micheline_size.of_nodenodeinletnodes=Saturation_repr.to_intnode_size.nodesinletstring_bytes=Saturation_repr.to_intnode_size.string_bytesinletz_bytes=Saturation_repr.to_intnode_size.z_bytesinCompare.Int.(nodes>Constants_repr.max_micheline_node_count||string_bytes+z_bytes>Constants_repr.max_micheline_bytes_limit)letexpand_nodecontextnode=(* We charge for traversing the top-level node at the beginning.
Inside the loop, we charge for traversing each new constant
that gets expanded. *)Raw_context.consume_gascontext(Gas_costs.expand_no_constants_branch_costnode)>>?=funcontext->bottom_up_fold_cps(* We carry a Boolean representing whether we
had to do any expansions or not. *)(context,Expr_hash_map.empty,false)node(fun(context,_,did_expansion)node->return(context,node,did_expansion))(fun(context,map,did_expansion)nodek->matchnodewith|Prim(_,H_constant,args,annot)->((* Charge for validating the b58check hash. *)Raw_context.consume_gascontextGas_costs.expand_constants_branch_cost>>?=funcontext->match(args,annot)with(* A constant Prim should always have a single String argument,
being a properly formatted hash. *)|[String(_,address)],[]->(matchScript_expr_hash.of_b58check_optaddresswith|None->tzfailBadly_formed_constant_expression|Somehash->(matchExpr_hash_map.findhashmapwith|Somenode->(* Charge traversing the newly retrieved node *)Raw_context.consume_gascontext(Gas_costs.expand_no_constants_branch_costnode)>>?=funcontext->k(context,map,true)node|None->getcontexthash>>=?fun(context,expr)->(* Charge traversing the newly retrieved node *)letnode=rootexprinRaw_context.consume_gascontext(Gas_costs.expand_no_constants_branch_costnode)>>?=funcontext->k(context,Expr_hash_map.addhashnodemap,true)node))|_->tzfailBadly_formed_constant_expression)|Int_|String_|Bytes_|Prim_|Seq_->k(context,map,did_expansion)node)>>=?fun(context,node,did_expansion)->ifdid_expansionthen(* Gas charged during expansion is at least proportional to the size of the
resulting node so the execution time of [node_too_large] is already
covered. *)ifnode_too_largenodethentzfailExpression_too_largeelsereturn(context,node)elsereturn(context,node)letexpandcontextexpr=expand_nodecontext(rootexpr)>|=?fun(context,node)->(context,strip_locationsnode)(** Computes the maximum depth of a Micheline node. Fails
with [Expression_too_deep] if greater than
[max_allowed_global_constant_depth].*)letcheck_depthnode=letrecadvancenodedepthk=ifCompare.Int.(depth>Constants_repr.max_allowed_global_constant_depth)thenerrorExpression_too_deepelsematchnodewith|Int_|String_|Bytes_|Prim(_,_,[],_)|Seq(_,[])->(k[@tailcall])(depth+1)|Prim(loc,_,hd::tl,_)|Seq(loc,hd::tl)->(advance[@tailcall])hd(depth+1)(fundhd->(advance[@tailcall])(* Because [depth] doesn't care about the content
of the expression, we can safely throw away information
about primitives and replace them with the [Seq] constructor.*)(Seq(loc,tl))depth(fundtl->(k[@tailcall])(Compare.Int.maxdhddtl)))inadvancenode0(funx->Okx)letregistercontextvalue=(* To calculate the total depth, we first expand all constants
in the expression. This may fail with [Expression_too_large].
Though the stored expression is the unexpanded version.
*)expand_nodecontext(rootvalue)>>=?fun(context,node)->(* We do not need to carbonate [check_depth]. [expand_node] and
[Storage.Global_constants.Map.init] are already carbonated
with gas at least proportional to the size of the expanded node
and the computation cost of [check_depth] is of the same order. *)check_depthnode>>?=fun(_depth:int)->expr_to_address_in_contextcontextvalue>>?=fun(context,key)->traceExpression_already_registered@@Storage.Global_constants.Map.initcontextkeyvalue>|=?fun(context,size)->(context,key,Z.of_intsize)moduleInternal_for_tests=structletnode_too_large=node_too_largeletbottom_up_fold_cps=bottom_up_fold_cpsletexpr_to_address_in_context=expr_to_address_in_contextend