diff --git a/src/proto_009_PsFLoren/lib_client/client_proto_context.ml b/src/proto_009_PsFLoren/lib_client/client_proto_context.ml index 4b0bfa6d2918bfadd3c9a91ca79371ef00991727..2f145e4317879301874fadcebc8b348d9085badd 100644 --- a/src/proto_009_PsFLoren/lib_client/client_proto_context.ml +++ b/src/proto_009_PsFLoren/lib_client/client_proto_context.ml @@ -36,8 +36,9 @@ let get_balance (rpc : #rpc_context) ~chain ~block contract = let get_storage (rpc : #rpc_context) ~chain ~block contract = Alpha_services.Contract.storage_opt rpc (chain, block) contract -let get_big_map_value (rpc : #rpc_context) ~chain ~block id key = - Alpha_services.Contract.big_map_get rpc (chain, block) id key +let get_big_map_value (rpc : #rpc_context) ~chain ~block id key ~unparsing_mode + = + Plugin.RPC.big_map_get_normalized rpc (chain, block) id key ~unparsing_mode let get_contract_big_map_value (rpc : #rpc_context) ~chain ~block contract key = diff --git a/src/proto_009_PsFLoren/lib_client/client_proto_context.mli b/src/proto_009_PsFLoren/lib_client/client_proto_context.mli index d662c3b4ab47afe3d121b8837178ab40d122f64f..da21870931a42125e1787b48d24e0f07594fe2ff 100644 --- a/src/proto_009_PsFLoren/lib_client/client_proto_context.mli +++ b/src/proto_009_PsFLoren/lib_client/client_proto_context.mli @@ -53,6 +53,7 @@ val get_big_map_value : block:Shell_services.block -> Big_map.Id.t -> Script_expr_hash.t -> + unparsing_mode:Script_ir_translator.unparsing_mode -> Script.expr tzresult Lwt.t val get_script : diff --git a/src/proto_009_PsFLoren/lib_client/client_proto_programs.ml b/src/proto_009_PsFLoren/lib_client/client_proto_programs.ml index 6910e290e784968156282a6c5eca4183d4a1bc27..54994ae0dfcc937da91fb807aefca18170fe0d3c 100644 --- a/src/proto_009_PsFLoren/lib_client/client_proto_programs.ml +++ b/src/proto_009_PsFLoren/lib_client/client_proto_programs.ml @@ -116,12 +116,11 @@ let run (cctxt : #Protocol_client_context.rpc_context) ?entrypoint () = Chain_services.chain_id cctxt ~chain () >>=? fun chain_id -> - Alpha_services.Helpers.Scripts.run_code + Plugin.RPC.run_code_normalized cctxt (chain, block) ?gas ?entrypoint - ~unparsing_mode ~script:program.expanded ~storage:storage.expanded ~input:input.expanded @@ -130,6 +129,7 @@ let run (cctxt : #Protocol_client_context.rpc_context) ~chain_id ~source ~payer + ~unparsing_mode let trace (cctxt : #Protocol_client_context.rpc_context) ~(chain : Chain_services.chain) ~block ?(amount = Tez.fifty_cents) ~balance @@ -140,12 +140,11 @@ let trace (cctxt : #Protocol_client_context.rpc_context) ?entrypoint () = Chain_services.chain_id cctxt ~chain () >>=? fun chain_id -> - Alpha_services.Helpers.Scripts.trace_code + Plugin.RPC.trace_code_normalized cctxt (chain, block) ?gas ?entrypoint - ~unparsing_mode ~script:program.expanded ~storage:storage.expanded ~input:input.expanded @@ -154,6 +153,7 @@ let trace (cctxt : #Protocol_client_context.rpc_context) ~chain_id ~source ~payer + ~unparsing_mode let typecheck_data cctxt ~(chain : Chain_services.chain) ~block ?gas ?legacy ~(data : Michelson_v1_parser.parsed) ~(ty : Michelson_v1_parser.parsed) () diff --git a/src/proto_009_PsFLoren/lib_client_commands/client_proto_context_commands.ml b/src/proto_009_PsFLoren/lib_client_commands/client_proto_context_commands.ml index b279afdda6574984f8df9f4f7018a1c703f5c0ba..f630118c65cd5da71280a163584f57f8dc5575f3 100644 --- a/src/proto_009_PsFLoren/lib_client_commands/client_proto_context_commands.ml +++ b/src/proto_009_PsFLoren/lib_client_commands/client_proto_context_commands.ml @@ -296,12 +296,16 @@ let commands network () = command ~group ~desc:"Get the storage of a contract." - no_options + (args1 (unparsing_mode_arg ~default:"Readable")) ( prefixes ["get"; "contract"; "storage"; "for"] @@ ContractAlias.destination_param ~name:"src" ~desc:"source contract" @@ stop ) - (fun () (_, contract) (cctxt : Protocol_client_context.full) -> - get_storage cctxt ~chain:cctxt#chain ~block:cctxt#block contract + (fun unparsing_mode (_, contract) (cctxt : Protocol_client_context.full) -> + Plugin.RPC.get_storage_normalized + cctxt + (cctxt#chain, cctxt#block) + ~contract + ~unparsing_mode >>=? function | None -> cctxt#error "This is not a smart contract." @@ -337,7 +341,7 @@ let commands network () = command ~group ~desc:"Get a value in a big map." - no_options + (args1 (unparsing_mode_arg ~default:"Readable")) ( prefixes ["get"; "element"] @@ Clic.param ~name:"key" @@ -350,25 +354,30 @@ let commands network () = ~desc:"identifier of the big_map" int_parameter @@ stop ) - (fun () key id (cctxt : Protocol_client_context.full) -> + (fun unparsing_mode key id (cctxt : Protocol_client_context.full) -> get_big_map_value cctxt ~chain:cctxt#chain ~block:cctxt#block (Big_map.Id.parse_z (Z.of_int id)) key + ~unparsing_mode >>=? fun value -> cctxt#answer "%a" Michelson_v1_printer.print_expr_unwrapped value >>= fun () -> return_unit); command ~group ~desc:"Get the code of a contract." - no_options + (args1 (unparsing_mode_arg ~default:"Readable")) ( prefixes ["get"; "contract"; "code"; "for"] @@ ContractAlias.destination_param ~name:"src" ~desc:"source contract" @@ stop ) - (fun () (_, contract) (cctxt : Protocol_client_context.full) -> - get_script cctxt ~chain:cctxt#chain ~block:cctxt#block contract + (fun unparsing_mode (_, contract) (cctxt : Protocol_client_context.full) -> + Plugin.RPC.get_script_normalized + cctxt + (cctxt#chain, cctxt#block) + ~contract + ~unparsing_mode >>=? function | None -> cctxt#error "This is not a smart contract." diff --git a/src/proto_009_PsFLoren/lib_plugin/plugin.ml b/src/proto_009_PsFLoren/lib_plugin/plugin.ml index 6eedbcb0848eb48199993f586946cf2b6caa8af3..f53acb115e498c30684728055aa430edd6d57ddd 100644 --- a/src/proto_009_PsFLoren/lib_plugin/plugin.ml +++ b/src/proto_009_PsFLoren/lib_plugin/plugin.ml @@ -234,6 +234,21 @@ end module RPC = struct open Environment + type Environment.Error_monad.error += Cannot_serialize_log_normalized + + let () = + (* Cannot serialize log *) + Environment.Error_monad.register_error_kind + `Temporary + ~id:"michelson_v1.cannot_serialize_log_normalized" + ~title:"Not enough gas to serialize normalized execution trace" + ~description: + "Execution trace with normalized stacks was to big to be serialized \ + with the provided gas" + Data_encoding.empty + (function Cannot_serialize_log_normalized -> Some () | _ -> None) + (fun () -> Cannot_serialize_log_normalized) + module Unparse_types = struct (* Same as the unparsing functions for types in Script_ir_translator but does not consume gas and never folds (pair a (pair b c)) *) @@ -286,12 +301,6 @@ module RPC = struct Prim (-1, T_option, [unparse_comparable_ty t], unparse_type_annot tname) - (* Uncomment when rebasing on top of Baking account *) - (* | Baker_hash_key tname -> - * Prim (-1, T_baker_hash, [], unparse_type_annot tname) - * | Pvss_key_key tname -> - * Prim (-1, T_pvss_key, [], unparse_type_annot tname) *) - let unparse_memo_size memo_size = let z = Alpha_context.Sapling.Memo_size.unparse_to_z memo_size in Int (-1, z) @@ -388,18 +397,71 @@ module RPC = struct ( T_sapling_state, [unparse_memo_size memo_size], unparse_type_annot tname ) - - (* Uncomment when rebasing on top of Baking account *) - (* | Baker_hash_t tname -> - * return (T_baker_hash, [], unparse_type_annot tname) - * | Pvss_key_t tname -> - * return (T_pvss_key, [], unparse_type_annot tname) - * | Baker_operation_t tname -> - * return (T_baker_operation, [], unparse_type_annot tname) *) end let helpers_path = RPC_path.(open_root / "helpers" / "scripts") + let contract_root = + ( RPC_path.(open_root / "context" / "contracts") + : RPC_context.t RPC_path.context ) + + let big_map_root = + ( RPC_path.(open_root / "context" / "big_maps") + : RPC_context.t RPC_path.context ) + + let unparsing_mode_encoding = + let open Data_encoding in + union + ~tag_size:`Uint8 + [ case + (Tag 0) + ~title:"Readable" + (constant "Readable") + (function + | Script_ir_translator.Readable -> + Some () + | Script_ir_translator.Optimized + | Script_ir_translator.Optimized_legacy -> + None) + (fun () -> Script_ir_translator.Readable); + case + (Tag 1) + ~title:"Optimized" + (constant "Optimized") + (function + | Script_ir_translator.Optimized -> + Some () + | Script_ir_translator.Readable + | Script_ir_translator.Optimized_legacy -> + None) + (fun () -> Script_ir_translator.Optimized); + case + (Tag 2) + ~title:"Optimized_legacy" + (constant "Optimized_legacy") + (function + | Script_ir_translator.Optimized_legacy -> + Some () + | Script_ir_translator.Readable | Script_ir_translator.Optimized -> + None) + (fun () -> Script_ir_translator.Optimized_legacy) ] + + let run_code_input_encoding = + let open Data_encoding in + merge_objs + (obj10 + (req "script" Script.expr_encoding) + (req "storage" Script.expr_encoding) + (req "input" Script.expr_encoding) + (req "amount" Tez.encoding) + (req "balance" Tez.encoding) + (req "chain_id" Chain_id.encoding) + (opt "source" Contract.encoding) + (opt "payer" Contract.encoding) + (opt "gas" Gas.Arith.z_integral_encoding) + (dft "entrypoint" string "default")) + (obj1 (req "unparsing_mode" unparsing_mode_encoding)) + let normalize_type = let open Data_encoding in RPC_service.post_service @@ -411,6 +473,119 @@ module RPC = struct ~query:RPC_query.empty RPC_path.(helpers_path / "normalize_type") + let get_storage_normalized = + let open Data_encoding in + RPC_service.post_service + ~description: + "Access the data of the contract and normalize it using the requested \ + unparsing mode." + ~input:(obj1 (req "unparsing_mode" unparsing_mode_encoding)) + ~query:RPC_query.empty + ~output:(option Script.expr_encoding) + RPC_path.(contract_root /: Contract.rpc_arg / "storage" / "normalized") + + let get_script_normalized = + let open Data_encoding in + RPC_service.post_service + ~description: + "Access the script of the contract and normalize it using the \ + requested unparsing mode." + ~input:(obj1 (req "unparsing_mode" unparsing_mode_encoding)) + ~query:RPC_query.empty + ~output:(option Script.encoding) + RPC_path.(contract_root /: Contract.rpc_arg / "script" / "normalized") + + let run_code_normalized = + let open Data_encoding in + RPC_service.post_service + ~description: + "Run a piece of code in the current context, normalize the output \ + using the requested unparsing mode." + ~query:RPC_query.empty + ~input:run_code_input_encoding + ~output: + (conv + (fun (storage, operations, lazy_storage_diff) -> + (storage, operations, lazy_storage_diff, lazy_storage_diff)) + (fun ( storage, + operations, + legacy_lazy_storage_diff, + lazy_storage_diff ) -> + let lazy_storage_diff = + match lazy_storage_diff with + | Some s -> + Some s + | None -> + legacy_lazy_storage_diff + in + (storage, operations, lazy_storage_diff)) + (obj4 + (req "storage" Script.expr_encoding) + (req + "operations" + (list Alpha_context.Operation.internal_operation_encoding)) + (opt "big_map_diff" Lazy_storage.legacy_big_map_diff_encoding) + (opt "lazy_storage_diff" Lazy_storage.encoding))) + RPC_path.(helpers_path / "run_code" / "normalized") + + let trace_encoding = + let open Data_encoding in + def "scripted.trace" @@ list + @@ obj3 + (req "location" Script.location_encoding) + (req "gas" Gas.encoding) + (req + "stack" + (list (obj2 (req "item" Script.expr_encoding) (opt "annot" string)))) + + let trace_code_normalized = + let open Data_encoding in + RPC_service.post_service + ~description: + "Run a piece of code in the current context, keeping a trace, \ + normalize the output using the requested unparsing mode." + ~query:RPC_query.empty + ~input:run_code_input_encoding + ~output: + (conv + (fun (storage, operations, trace, lazy_storage_diff) -> + (storage, operations, trace, lazy_storage_diff, lazy_storage_diff)) + (fun ( storage, + operations, + trace, + legacy_lazy_storage_diff, + lazy_storage_diff ) -> + let lazy_storage_diff = + match lazy_storage_diff with + | Some s -> + Some s + | None -> + legacy_lazy_storage_diff + in + (storage, operations, trace, lazy_storage_diff)) + (obj5 + (req "storage" Script.expr_encoding) + (req + "operations" + (list Alpha_context.Operation.internal_operation_encoding)) + (req "trace" trace_encoding) + (opt "big_map_diff" Lazy_storage.legacy_big_map_diff_encoding) + (opt "lazy_storage_diff" Lazy_storage.encoding))) + RPC_path.(helpers_path / "trace_code" / "normalized") + + let big_map_get_normalized = + let open Data_encoding in + RPC_service.post_service + ~description: + "Access the value associated with a key in a big map, normalize the \ + output using the requested unparsing mode." + ~query:RPC_query.empty + ~input:(obj1 (req "unparsing_mode" unparsing_mode_encoding)) + ~output:Script.expr_encoding + RPC_path.( + big_map_root /: Big_map.Id.rpc_arg /: Script_expr_hash.rpc_arg + / "normalized") + let rpc_services = let patched_services = ref (RPC_directory.empty : Updater.rpc_context RPC_directory.t) @@ -421,6 +596,52 @@ module RPC = struct Services_registration.rpc_init ctxt >>=? fun ctxt -> f ctxt q i) in let register0 s f = register0_fullctxt s (fun {context; _} -> f context) in + let register1_fullctxt s f = + patched_services := + RPC_directory.register !patched_services s (fun (ctxt, arg) q i -> + Services_registration.rpc_init ctxt >>=? fun ctxt -> f ctxt arg q i) + in + let register1 s f = + register1_fullctxt s (fun {context; _} x -> f context x) + in + let _register1_noctxt s f = + patched_services := + RPC_directory.register !patched_services s (fun (_, arg) q i -> + f arg q i) + in + let register2_fullctxt s f = + patched_services := + RPC_directory.register + !patched_services + s + (fun ((ctxt, arg1), arg2) q i -> + Services_registration.rpc_init ctxt + >>=? fun ctxt -> f ctxt arg1 arg2 q i) + in + let register2 s f = + register2_fullctxt s (fun {context; _} a1 a2 q i -> f context a1 a2 q i) + in + let register_field s f = + register1 s (fun ctxt contract () () -> + Contract.exists ctxt contract + >>=? function true -> f ctxt contract | false -> raise Not_found) + in + let _register_opt_field s f = + register_field s (fun ctxt a1 -> + f ctxt a1 >|=? function None -> raise Not_found | Some v -> v) + in + let originate_dummy_contract ctxt script balance = + let ctxt = Contract.init_origination_nonce ctxt Operation_hash.zero in + Lwt.return (Contract.fresh_contract_from_current_nonce ctxt) + >>=? fun (ctxt, dummy_contract) -> + Contract.originate + ctxt + dummy_contract + ~balance + ~delegate:None + ~script:(script, None) + >>=? fun ctxt -> return (ctxt, dummy_contract) + in register0 normalize_type (fun ctxt () typ -> let open Script_ir_translator in let ctxt = Gas.set_unlimited ctxt in @@ -436,8 +657,305 @@ module RPC = struct >>?= fun (Ex_ty typ, _ctxt) -> let normalized = Unparse_types.unparse_ty typ in return @@ Micheline.strip_locations normalized) ; + (* Patched RPC: get_storage *) + register1 get_storage_normalized (fun ctxt contract () unparsing_mode -> + Contract.get_script ctxt contract + >>=? fun (ctxt, script) -> + match script with + | None -> + return_none + | Some script -> + let ctxt = Gas.set_unlimited ctxt in + let open Script_ir_translator in + parse_script ctxt ~legacy:true ~allow_forged_in_storage:true script + >>=? fun (Ex_script script, ctxt) -> + unparse_script ctxt unparsing_mode script + >>=? fun (script, ctxt) -> + Script.force_decode_in_context ctxt script.storage + >>?= fun (storage, _ctxt) -> return_some storage) ; + (* Patched RPC: get_script *) + register1 get_script_normalized (fun ctxt contract () unparsing_mode -> + Contract.get_script ctxt contract + >>=? fun (ctxt, script) -> + match script with + | None -> + return_none + | Some script -> + let ctxt = Gas.set_unlimited ctxt in + let open Script_ir_translator in + parse_script ctxt ~legacy:true ~allow_forged_in_storage:true script + >>=? fun (Ex_script script, ctxt) -> + unparse_script ctxt unparsing_mode script + >>=? fun (script, _ctxt) -> return_some script) ; + register0 + run_code_normalized + (fun ctxt + () + ( ( code, + storage, + parameter, + amount, + balance, + chain_id, + source, + payer, + gas, + entrypoint ), + unparsing_mode ) + -> + let storage = Script.lazy_expr storage in + let code = Script.lazy_expr code in + originate_dummy_contract ctxt {storage; code} balance + >>=? fun (ctxt, dummy_contract) -> + let (source, payer) = + match (source, payer) with + | (Some source, Some payer) -> + (source, payer) + | (Some source, None) -> + (source, source) + | (None, Some payer) -> + (payer, payer) + | (None, None) -> + (dummy_contract, dummy_contract) + in + let gas = + match gas with + | Some gas -> + gas + | None -> + Constants.hard_gas_limit_per_operation ctxt + in + let ctxt = Gas.set_limit ctxt gas in + let step_constants = + let open Script_interpreter in + {source; payer; self = dummy_contract; amount; chain_id} + in + Script_interpreter.execute + ctxt + unparsing_mode + step_constants + ~script:{storage; code} + ~entrypoint + ~parameter + ~internal:true + >|=? fun {Script_interpreter.storage; operations; lazy_storage_diff; _} -> + (storage, operations, lazy_storage_diff)) ; + register0 + trace_code_normalized + (fun ctxt + () + ( ( code, + storage, + parameter, + amount, + balance, + chain_id, + source, + payer, + gas, + entrypoint ), + unparsing_mode ) + -> + let module Traced_interpreter = struct + type log_element = + | Log : + context * Script.location * 'a * 'a Script_typed_ir.stack_ty + -> log_element + + let unparse_stack ctxt (stack, stack_ty) = + (* We drop the gas limit as this function is only used for debugging/errors. *) + let ctxt = Gas.set_unlimited ctxt in + let rec unparse_stack : + type a. + a Script_typed_ir.stack_ty * a -> + (Script.expr * string option) list + Environment.Error_monad.tzresult + Lwt.t = function + | (Empty_t, ()) -> + return_nil + | (Item_t (ty, rest_ty, annot), (v, rest)) -> + Script_ir_translator.unparse_data ctxt unparsing_mode ty v + >>=? fun (data, _ctxt) -> + unparse_stack (rest_ty, rest) + >|=? fun rest -> + let annot = + match Script_ir_annot.unparse_var_annot annot with + | [] -> + None + | [a] -> + Some a + | _ -> + assert false + in + let data = Micheline.strip_locations data in + (data, annot) :: rest + in + unparse_stack (stack_ty, stack) + + module Trace_logger () : Script_interpreter.STEP_LOGGER = struct + let log : log_element list ref = ref [] + + let log_interp ctxt (descr : (_, _) Script_typed_ir.descr) stack = + log := Log (ctxt, descr.loc, stack, descr.bef) :: !log + + let log_entry _ctxt _descr _stack = () + + let log_exit ctxt (descr : (_, _) Script_typed_ir.descr) stack = + log := Log (ctxt, descr.loc, stack, descr.aft) :: !log + + let get_log () = + Environment.Error_monad.map_s + (fun (Log (ctxt, loc, stack, stack_ty)) -> + Environment.Error_monad.trace + Cannot_serialize_log_normalized + (unparse_stack ctxt (stack, stack_ty)) + >>=? fun stack -> return (loc, Gas.level ctxt, stack)) + !log + >>=? fun res -> return (Some (List.rev res)) + end + end in + let storage = Script.lazy_expr storage in + let code = Script.lazy_expr code in + originate_dummy_contract ctxt {storage; code} balance + >>=? fun (ctxt, dummy_contract) -> + let (source, payer) = + match (source, payer) with + | (Some source, Some payer) -> + (source, payer) + | (Some source, None) -> + (source, source) + | (None, Some payer) -> + (payer, payer) + | (None, None) -> + (dummy_contract, dummy_contract) + in + let gas = + match gas with + | Some gas -> + gas + | None -> + Constants.hard_gas_limit_per_operation ctxt + in + let ctxt = Gas.set_limit ctxt gas in + let step_constants = + let open Script_interpreter in + {source; payer; self = dummy_contract; amount; chain_id} + in + let module Logger = Traced_interpreter.Trace_logger () in + let logger = (module Logger : Script_interpreter.STEP_LOGGER) in + Script_interpreter.execute + ~logger + ctxt + unparsing_mode + step_constants + ~script:{storage; code} + ~entrypoint + ~parameter + ~internal:true + >>=? fun {storage; lazy_storage_diff; operations; _} -> + Logger.get_log () + >|=? fun trace -> + let trace = Option.value ~default:[] trace in + (storage, operations, trace, lazy_storage_diff)) ; + register2 big_map_get_normalized (fun ctxt id key () unparsing_mode -> + let open Script_ir_translator in + let ctxt = Gas.set_unlimited ctxt in + Big_map.exists ctxt id + >>=? fun (ctxt, types) -> + match types with + | None -> + raise Not_found + | Some (_, value_type) -> ( + parse_big_map_value_ty + ctxt + ~legacy:true + (Micheline.root value_type) + >>?= fun (Ex_ty value_type, ctxt) -> + Big_map.get_opt ctxt id key + >>=? fun (_ctxt, value) -> + match value with + | None -> + raise Not_found + | Some value -> + parse_data + ctxt + ~legacy:true + ~allow_forged:true + value_type + (Micheline.root value) + >>=? fun (value, ctxt) -> + unparse_data ctxt unparsing_mode value_type value + >|=? fun (value, _ctxt) -> Micheline.strip_locations value )) ; RPC_directory.merge rpc_services !patched_services let normalize_type ctxt block ~ty = RPC_context.make_call0 normalize_type ctxt block () ty + + let get_storage_normalized ctxt block ~contract ~unparsing_mode = + RPC_context.make_call1 + get_storage_normalized + ctxt + block + contract + () + unparsing_mode + + let get_script_normalized ctxt block ~contract ~unparsing_mode = + RPC_context.make_call1 + get_script_normalized + ctxt + block + contract + () + unparsing_mode + + let run_code_normalized ctxt block ?gas ?(entrypoint = "default") ~script + ~storage ~input ~amount ~balance ~chain_id ~source ~payer ~unparsing_mode + = + RPC_context.make_call0 + run_code_normalized + ctxt + block + () + ( ( script, + storage, + input, + amount, + balance, + chain_id, + source, + payer, + gas, + entrypoint ), + unparsing_mode ) + + let trace_code_normalized ctxt block ?gas ?(entrypoint = "default") ~script + ~storage ~input ~amount ~balance ~chain_id ~source ~payer ~unparsing_mode + = + RPC_context.make_call0 + trace_code_normalized + ctxt + block + () + ( ( script, + storage, + input, + amount, + balance, + chain_id, + source, + payer, + gas, + entrypoint ), + unparsing_mode ) + + let big_map_get_normalized ctxt block id key ~unparsing_mode = + RPC_context.make_call2 + big_map_get_normalized + ctxt + block + id + key + () + unparsing_mode end