123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283(*****************************************************************************)(* *)(* Open Source License *)(* Copyright (c) 2021 Trili Tech, <contact@trili.tech> *)(* *)(* 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. *)(* *)(*****************************************************************************)openAlpha_contexttypeerror+=Failed_to_load_big_map_value_typeofBig_map.Id.tlet()=letopenData_encodinginregister_error_kind`Permanent~id:"Failed_to_load_big_map_value_type"~title:"Failed to load big-map value type"~description:"Failed to load big-map value type when computing ticket diffs."~pp:(funppfbig_map_id->Format.fprintfppf"Failed to load big-map value type for big-map-id: '%a'"Z.pp_print(Big_map.Id.unparse_to_zbig_map_id))(obj1(req"big_map_id"Big_map.Id.encoding))(function|Failed_to_load_big_map_value_typebig_map_id->Somebig_map_id|_->None)(funbig_map_id->Failed_to_load_big_map_value_typebig_map_id)(** Extracts the ticket-token and amount from an ex_ticket value. *)lettoken_and_amountctxtex_ticket=Gas.consumectxtTicket_costs.Constants.cost_collect_tickets_step>|?functxt->lettoken,amount=Ticket_scanner.ex_token_and_amount_of_ex_ticketex_ticketin((token,Script_int.(to_zint(amount:>nnum))),ctxt)(** Extracts the ticket-token and amount from an ex_ticket value and returns
the opposite of the amount. This is used to account for removal of tickets inside
big maps when either a ticket is taken out of a big map or a whole big map is
dropped. *)letneg_token_and_amountctxtex_ticket=token_and_amountctxtex_ticket>>?fun((token,amount),ctxt)->Gas.consumectxt(Ticket_costs.negate_costamount)>|?functxt->((token,Z.negamount),ctxt)letparse_value_typectxtvalue_type=Script_ir_translator.parse_big_map_value_tyctxt~legacy:true(Micheline.rootvalue_type)(** Collects all ticket-token balances contained in the given node and prepends
them to the accumulator [acc]. The given [get_token_and_amount] function
extracts the ticket-token and amount (either positive or negative) from an
[ex_ticket] value, depending on whether the diff stems from adding or
removing a value containing tickets. *)letcollect_token_diffs_of_nodectxthas_ticketsnode~get_token_and_amountacc=Ticket_scanner.tickets_of_nodectxt(* It's currently not possible to have nested lazy structures, but this is
for future proofing. *)~include_lazy:truehas_tickets(Micheline.rootnode)>>=?fun(ex_tickets,ctxt)->List.fold_left_e(fun(acc,ctxt)ticket->get_token_and_amountctxtticket>|?fun(item,ctxt)->(item::acc,ctxt))(acc,ctxt)ex_tickets>>?=return(** A module for keeping track of script-key-hashes. It's used for looking up
keys for multiple big-map updates referencing the same key.
*)moduleKey_hash_map=Carbonated_map.Make(structtypecontext=Alpha_context.contextletconsume=Alpha_context.Gas.consumeend)(structtypet=Script_expr_hash.tletcompare=Script_expr_hash.compareletcompare_cost_=Ticket_costs.Constants.cost_compare_ticket_hashend)(** Collects all ticket-token diffs from a big-map update and prepends them
to the accumulator [acc]. *)letcollect_token_diffs_of_big_map_updatectxt~big_map_idhas_tickets{Lazy_storage_kind.Big_map.key=_;key_hash;value}already_updatedacc=letcollect_token_diffs_of_node_optionctxt~get_token_and_amountexpr_optacc=matchexpr_optwith|Someexpr->collect_token_diffs_of_nodectxthas_ticketsexpr~get_token_and_amountacc|None->return(acc,ctxt)in(* First check if the key-hash has already been updated, in that case pull the
value from the [already_updated] map. Note that this should not happen with
the current implementation of big-map overlays as it guarantees that keys
are unique. The extra check is used for future proofing.
*)(Key_hash_map.findctxtkey_hashalready_updated>>?=fun(val_opt,ctxt)->matchval_optwith|Someupdated_value->return(updated_value,ctxt)|None->(* Load tickets from the old value that was removed. *)Big_map.get_optctxtbig_map_idkey_hash>|=?fun(ctxt,old_value)->(old_value,ctxt))>>=?fun(old_value,ctxt)->collect_token_diffs_of_node_optionctxt~get_token_and_amount:neg_token_and_amountold_valueacc>>=?fun(acc,ctxt)->Key_hash_map.updatectxtkey_hash(functxt_->ok(Somevalue,ctxt))already_updated>>?=fun(already_updated,ctxt)->(* TODO: #2303
Avoid re-parsing the value.
In order to find tickets from the new value, we need to parse it. It would
be more efficient if the value was already present.
*)collect_token_diffs_of_node_optionctxt~get_token_and_amount:token_and_amountvalueacc>|=?fun(tickets,ctxt)->(tickets,already_updated,ctxt)(** Collects all ticket-token diffs from a list of big-map updates and prepends
them to the accumulator [acc]. *)letcollect_token_diffs_of_big_map_updatesctxtbig_map_id~value_typeupdatesacc=(* TODO: #2303
Avoid re-parsing the value type.
We should have the non-serialized version of the value type.
*)parse_value_typectxtvalue_type>>?=fun(Script_typed_ir.Ex_tyvalue_type,ctxt)->Ticket_scanner.type_has_ticketsctxtvalue_type>>?=fun(has_tickets,ctxt)->List.fold_left_es(fun(acc,already_updated,ctxt)update->collect_token_diffs_of_big_map_updatectxt~big_map_idhas_ticketsupdatealready_updatedacc)(acc,Key_hash_map.empty,ctxt)updates>|=?fun(acc,_already_updated,ctxt)->(acc,ctxt)(** Given a big-map id, this function collects ticket-token diffs and prepends
them to the accumulator [acc]. *)letcollect_token_diffs_of_big_mapctxt~get_token_and_amountbig_map_idacc=Gas.consumectxtTicket_costs.Constants.cost_collect_tickets_step>>?=functxt->Big_map.existsctxtbig_map_id>>=?fun(ctxt,key_val_tys)->matchkey_val_tyswith|Some(_key_ty,value_ty)->(* TODO: #2303
Avoid re-parsing the value type.
In order to find tickets from the value, we need to parse the value
type. It would be more efficient if the value preserved.
*)parse_value_typectxtvalue_ty>>?=fun(Script_typed_ir.Ex_tyvalue_type,ctxt)->Ticket_scanner.type_has_ticketsctxtvalue_type>>?=fun(has_tickets,ctxt)->(* Iterate over big-map items. *)Big_map.list_key_valuesctxtbig_map_id>>=?fun(ctxt,exprs)->List.fold_left_es(fun(acc,ctxt)(_key_hash,node)->collect_token_diffs_of_nodectxthas_ticketsnode~get_token_and_amountacc)(acc,ctxt)exprs|None->tzfail(Failed_to_load_big_map_value_typebig_map_id)(** Collects ticket-token diffs from a big-map and a list of updates, and
prepends them to the given accumulator [acc]. *)letcollect_token_diffs_of_big_map_and_updatesctxtbig_map_idupdatesacc=Gas.consumectxtTicket_costs.Constants.cost_collect_tickets_step>>?=functxt->Big_map.existsctxtbig_map_id>>=?fun(ctxt,key_val_opt)->matchkey_val_optwith|Some(_val,value_type)->collect_token_diffs_of_big_map_updatesctxtbig_map_id~value_typeupdatesacc|None->tzfail(Failed_to_load_big_map_value_typebig_map_id)(** Inspects the given [Lazy_storage.diffs_item] and prepends all ticket-token
diffs, resulting from the updates, to the given accumulator [acc]. *)letcollect_token_diffs_of_big_map_diffctxtdiff_itemacc=Gas.consumectxtTicket_costs.Constants.cost_collect_tickets_step>>?=functxt->matchdiff_itemwith|Lazy_storage.Item(Lazy_storage_kind.Big_map,big_map_id,Remove)->(* Collect all removed tokens from the big-map. *)collect_token_diffs_of_big_mapctxt~get_token_and_amount:neg_token_and_amountbig_map_idacc|Item(Lazy_storage_kind.Big_map,big_map_id,Update{init;updates})->(matchinitwith|Lazy_storage.Existing->(* Collect token diffs from the updates to the big-map. *)collect_token_diffs_of_big_map_and_updatesctxtbig_map_idupdatesacc|Copy{src}->(* Collect tokens diffs from the source of the copied big-map. *)collect_token_diffs_of_big_mapctxt~get_token_and_amount:token_and_amountsrcacc>>=?fun(acc,ctxt)->(* Collect token diffs from the updates to the copied big-map. *)collect_token_diffs_of_big_map_and_updatesctxtsrcupdatesacc|Alloc{key_type=_;value_type}->collect_token_diffs_of_big_map_updatesctxtbig_map_id~value_typeupdatesacc)|Item(Sapling_state,_,_)->return(acc,ctxt)letticket_diffs_of_lazy_storage_diffctxtdiffs_items=List.fold_left_es(fun(acc,ctxt)diff_item->collect_token_diffs_of_big_map_diffctxtdiff_itemacc)([],ctxt)diffs_items