https://gitlab.com/tezos/tezos
Tip revision: 87f419bf9b9092dc74afe41ca3a098dea2bdb9c2 authored by Alain Mebsout on 04 March 2024, 15:04:03 UTC
WIP: tezt test
WIP: tezt test
Tip revision: 87f419b
get_contracts.ml
(*****************************************************************************)
(* *)
(* Open Source License *)
(* Copyright (c) 2022 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. *)
(* *)
(*****************************************************************************)
open Tezos_clic
let mkdir dirname =
try Unix.mkdir dirname 0o775 with Unix.Unix_error (Unix.EEXIST, _, _) -> ()
let assert_ok ~__LOC__ = function
| Ok x -> x
| Error _ -> Stdlib.failwith ("Assertion failure at " ^ __LOC__)
module Make (P : Sigs.PROTOCOL) : Sigs.MAIN = struct
type serialization_costs = {decode : int; encode : int}
module Storage_helpers = struct
include Storage_helpers
let decode_and_costs expr =
match P.Script.decode_and_costs expr with
| Ok res -> Ok res
| Error err ->
Format.eprintf
"Failed decoding expression:\n%a\n"
Error_monad.pp_print_trace
err ;
assert (not Config.fatal) ;
Error `AlreadyWarned
let get_lazy_expr ~what ~getter ~pp ctxt x =
let open Lwt_result_syntax in
let* _, expr = get_value ~what ~getter ~pp ctxt x in
let*? expr, decode, encode = decode_and_costs expr in
return (expr, {decode; encode})
end
module ExprMap = Map.Make (P.Script.Hash)
type gas = {
code_costs : serialization_costs;
storage_costs : serialization_costs;
parsing_toplevel_cost : int;
parsing_storage_type_cost : int;
parsing_storage_cost : int;
unparsing_storage_cost : int;
}
let gas_headers, gas_to_list =
(* [gas_to_list] returns the field values corresponding to the [gas_headers].
Hence it should return lists of the same length as [gas_headers]. *)
let headers =
(List.flatten
@@ List.map
(fun name -> [name ^ "_decode"; name ^ "_encode"])
["code"; "storage"])
@ [
"parsing_toplevel";
"parsing_storage_type";
"parsing_storage";
"unparsing_storage";
]
in
let to_list
{
code_costs;
storage_costs;
parsing_toplevel_cost;
parsing_storage_type_cost;
parsing_storage_cost;
unparsing_storage_cost;
} =
(List.flatten
@@ List.map
(fun {decode; encode} -> [decode; encode])
[code_costs; storage_costs])
@ [
parsing_toplevel_cost;
parsing_storage_type_cost;
parsing_storage_cost;
unparsing_storage_cost;
]
in
(headers, to_list)
type storage = {
contract : P.Contract.repr;
storage : P.Script.expr;
gas : gas option;
}
type contract = {
script : P.Script.expr;
addresses : P.Contract.repr list;
storages : storage ExprMap.t;
}
module File_helpers = struct
let print_to_file ?err filename fmt =
Format.kasprintf
(fun s ->
let chan = open_out filename in
(try output_string chan s
with exn ->
flush chan ;
close_out chan ;
Option.iter (fun f -> f ()) err ;
raise exn) ;
flush chan ;
close_out chan ;
match err with
| None -> ()
| Some err ->
let input_chan = open_in filename in
let file_length = in_channel_length input_chan in
close_in input_chan ;
if file_length < 2 then err ())
fmt
let print_legacy_file ~dirname ~ext ~hash_string flag =
let filename = Filename.concat dirname (hash_string ^ ext) in
let err () =
Format.eprintf "Could not print legacy flag file %s\n\n" filename
in
print_to_file ~err filename "%s@." (if flag then "--legacy" else "")
let print_expr_file ~dirname ~ext ~hash_string expr =
let filename = Filename.concat dirname (hash_string ^ ext) in
let err () = Format.eprintf "Could not print expr file %s\n\n" filename in
print_to_file ~err filename "%a@." P.Script.print_expr expr
let print_expr_dir ~dirname ~ext exprs =
if not (ExprMap.is_empty exprs) then
let () = mkdir dirname in
ExprMap.iter
(fun hash expr ->
let hash_string = P.Script.Hash.to_b58check hash in
print_expr_file ~dirname ~ext ~hash_string expr)
exprs
let print_legacy_dir ~dirname ~ext exprs =
if not (ExprMap.is_empty exprs) then
let () = mkdir dirname in
ExprMap.iter
(fun hash flag ->
let hash_string = P.Script.Hash.to_b58check hash in
print_legacy_file ~dirname ~ext ~hash_string flag)
exprs
end
module Michelson_helpers = struct
let hash_expr expr =
let open P in
let bytes = Data_encoding.Binary.to_bytes_exn Script.expr_encoding expr in
Script.Hash.hash_bytes [bytes]
let parse_ty ctxt type_expr =
let hashed_ty = hash_expr type_expr in
let ex_ty, parse_ty_cost =
assert_ok ~__LOC__
@@ P.Translator.parse_ty
ctxt
~allow_lazy_storage:true
~allow_operation:true
~allow_contract:true
~allow_ticket:true
(Micheline.root type_expr)
in
(hashed_ty, ex_ty, parse_ty_cost)
type get_script_storage_type_result = {
storage_type_hash : P.Script.Hash.t;
storage_type : P.Translator.ex_ty;
parsing_toplevel_cost : int;
parsing_storage_type_cost : int;
}
let get_script_storage_type ctxt script_expr =
let open Lwt_syntax in
let+ result = P.Translator.parse_toplevel ctxt script_expr in
match result with
| Ok (code, parsing_toplevel_cost) ->
let storage_type_expr =
Micheline.strip_locations @@ P.code_storage_type code
in
let storage_type_hash, storage_type, parsing_storage_type_cost =
parse_ty ctxt storage_type_expr
in
{
storage_type_hash;
storage_type;
parsing_storage_type_cost;
parsing_toplevel_cost;
}
| Error _ ->
P.Script.print_expr Format.std_formatter script_expr ;
assert false
module BytesSet = Set.Make (Bytes)
let rec expr_collect_bytes acc expr =
let open Micheline in
match expr with
| Int _ -> acc
| String (_loc, s) ->
(* Just in case... *) BytesSet.add (Bytes.of_string s) acc
| Bytes (_loc, b) -> BytesSet.add b acc
| Prim (_loc, _, exprs, _) | Seq (_loc, exprs) ->
List.fold_left expr_collect_bytes acc exprs
let rec expr_collect_unpack_types ~parse_ty acc expr =
let open Micheline in
match expr with
| Int _ | String _ | Bytes _ -> acc
| Prim (_, prim, [ty], _) when P.is_unpack prim ->
let ty_expr = Micheline.strip_locations ty in
let ty_hash, ty = parse_ty ty_expr in
ExprMap.add ty_hash ty acc
| Prim (_, _, exprs, _) | Seq (_, exprs) ->
List.fold_left (expr_collect_unpack_types ~parse_ty) acc exprs
let try_decode_expr bytes =
let try_decode bytes =
match
Data_encoding.Binary.of_bytes_opt P.Script.expr_encoding bytes
with
| Some x -> [x]
| None -> []
| exception _ -> []
in
try_decode bytes
@
if Bytes.length bytes >= 1 && Bytes.get bytes 0 = '\005' then
try_decode (Bytes.sub bytes 1 (Bytes.length bytes - 1))
else []
let unpack_transitive_closure exprs =
let bytes =
ExprMap.fold
(fun _hash (expr, _from_unpack, _types) bytes ->
expr_collect_bytes bytes (Micheline.root expr))
exprs
BytesSet.empty
in
let rec aux exprs bytes =
let exprs, new_exprs =
BytesSet.fold
(fun bytes (exprs, new_exprs) ->
try_decode_expr bytes
|> List.fold_left
(fun (exprs, new_exprs) expr ->
let expr_hash = hash_expr expr in
match ExprMap.find expr_hash exprs with
| None ->
let exprs =
ExprMap.add
expr_hash
(expr, true, ExprMap.empty)
exprs
in
(exprs, expr :: new_exprs)
| Some (_, true, _) -> (exprs, new_exprs)
| Some (_, false, types) ->
let exprs =
ExprMap.add expr_hash (expr, true, types) exprs
in
(exprs, new_exprs))
(exprs, new_exprs))
bytes
(exprs, [])
in
match new_exprs with
| [] -> exprs
| _ :: _ ->
let bytes =
List.fold_left
(fun bytes expr ->
expr_collect_bytes bytes (Micheline.root expr))
BytesSet.empty
new_exprs
in
aux exprs bytes
in
aux exprs bytes
let collect_unpack_types ~parse_ty exprs =
ExprMap.fold
(fun _hash (expr, _, _) types ->
expr_collect_unpack_types ~parse_ty types (Micheline.root expr))
exprs
ExprMap.empty
let keep_lambda_types types =
ExprMap.fold
(fun hash ty types ->
match P.Lambda.collect_lambda_tys ty with
| None -> types
| Some ex_ty_lambdas -> ExprMap.add hash ex_ty_lambdas types)
types
ExprMap.empty
let collect_lambdas_in_exprs ctxt exprs unpack_types =
let open Lwt_syntax in
let unpack_types = keep_lambda_types unpack_types in
ExprMap.fold
(fun _hash (expr, from_unpack, types) lambdas_lwt ->
let* lambdas = lambdas_lwt in
let types = keep_lambda_types types in
let types =
if from_unpack then
ExprMap.union (fun _k l _r -> Some l) types unpack_types
else types
in
ExprMap.fold
(fun _ty_hash ex_ty_lambdas lambdas_lwt ->
let* lambdas = lambdas_lwt in
P.Lambda.fold_ex_ty_lambdas
~ctxt
~expr:(Micheline.root expr)
~acc:lambdas
~f:(fun acc ty_node lam_nodes ->
List.fold_left
(fun (acc_lambdas, acc_ty, acc_legacy) lambda ->
let ty_expr = Micheline.strip_locations ty_node in
let lam_expr =
Micheline.strip_locations @@ P.Lambda.lam_node lambda
in
let lam_hash = hash_expr lam_expr in
let acc_lambdas =
ExprMap.add lam_hash lam_expr acc_lambdas
in
let acc_ty = ExprMap.add lam_hash ty_expr acc_ty in
let acc_legacy =
ExprMap.add lam_hash (not from_unpack) acc_legacy
in
(acc_lambdas, acc_ty, acc_legacy))
acc
lam_nodes)
ex_ty_lambdas)
types
(Lwt.return lambdas))
exprs
(Lwt.return (ExprMap.empty, ExprMap.empty, ExprMap.empty))
let add_expr_to_map ctxt contract (m, i) =
let open Lwt_syntax in
let i = i + 1 in
if i mod 5000 = 0 then Format.printf "%d@." i ;
if P.Contract.is_implicit contract then Lwt.return (m, i)
else
let* code_opt =
Storage_helpers.get_lazy_expr
~what:"contract code"
~getter:P.Contract.get_code
~pp:P.Contract.pp
ctxt
contract
in
match code_opt with
| Error `AlreadyWarned -> Lwt.return (m, i)
| Ok (script, code_costs) ->
let* ctxt, script = P.Global_constants.expand ctxt script in
let+ add_storage =
if Config.(collect_lambdas || collect_storage || collect_gas) then
let* storage_opt =
Storage_helpers.get_lazy_expr
~what:"contract storage"
~getter:P.Contract.get_storage
~pp:P.Contract.pp
ctxt
contract
in
match storage_opt with
| Error `AlreadyWarned -> return @@ fun x -> x
| Ok (storage, storage_costs) ->
let+ gas =
if Config.collect_gas then
let* {
storage_type_hash = _;
storage_type = Ex_ty storage_type;
parsing_storage_type_cost;
parsing_toplevel_cost;
} =
get_script_storage_type ctxt script
in
let* storage_parsing_result =
P.Translator.parse_data
ctxt
~allow_forged:true
storage_type
(Micheline.root storage)
in
let parsed_storage, parsing_storage_cost =
assert_ok ~__LOC__ storage_parsing_result
in
let+ storage_unparsing_result =
P.Translator.unparse_data_cost
ctxt
storage_type
parsed_storage
in
let unparsing_storage_cost =
assert_ok ~__LOC__ storage_unparsing_result
in
Some
{
code_costs;
storage_costs;
parsing_toplevel_cost;
parsing_storage_type_cost;
parsing_storage_cost;
unparsing_storage_cost;
}
else return None
in
let key = hash_expr storage in
fun storages ->
ExprMap.add key {contract; storage; gas} storages
else Lwt.return (fun x -> x)
in
let key = hash_expr script in
( ExprMap.update
key
(fun existing ->
let contracts, storages =
match existing with
| Some {addresses; storages; _} -> (addresses, storages)
| None -> ([], ExprMap.empty)
in
let addresses = contract :: contracts in
let storages = add_storage storages in
Some {script; addresses; storages})
m,
i )
end
let big_map_fold :
P.context * P.Storage.big_map_id ->
init:'a ->
f:
(P.Script.prim Tezos_micheline.Micheline.canonical ->
'a ->
'a Error_monad.tzresult Lwt.t) ->
'a Error_monad.tzresult Lwt.t =
fun ctxt_i ~init ~f ->
let open Lwt_result_syntax in
let* _ctxt, values = P.Storage.list_values ctxt_i in
List.fold_left_es (fun acc v -> f v acc) init values
let output_contract_results ctxt output_dir hash ctr total_size =
let open Lwt_result_syntax in
let hash_string = P.Script.Hash.to_b58check hash in
let filename ~ext =
Filename.concat output_dir (Format.sprintf "%s.%s" hash_string ext)
in
let () =
if Config.print_contracts then
File_helpers.print_expr_file
~dirname:output_dir
~ext:".tz"
~hash_string
ctr.script ;
File_helpers.print_to_file
(filename ~ext:"address")
"%a"
(Format.pp_print_list ~pp_sep:Format.pp_print_newline P.Contract.pp)
ctr.addresses
in
let* contract_size =
if Config.measure_code_size then (
let* script_code =
P.Translator.parse_code ctxt @@ P.Script.lazy_expr ctr.script
in
let size =
Contract_size.
{
expected = P.Translator.expected_code_size script_code;
actual = P.Translator.actual_code_size script_code;
}
in
File_helpers.print_to_file
(filename ~ext:"size")
"%a"
Contract_size.pp
size ;
return size)
else return Contract_size.zero
in
(if Config.collect_storage then
let dirname = Filename.concat output_dir (hash_string ^ ".storage") in
let storages = ExprMap.map (fun {storage; _} -> storage) ctr.storages in
File_helpers.print_expr_dir ~dirname ~ext:".storage" storages) ;
return @@ Contract_size.add contract_size total_size
let write_gas_file ~output_dir contract_map =
let file = Filename.concat output_dir "gas.csv" in
Format.printf "Writing gas file %s...\n" file ;
let pp_sep fmt () = Format.pp_print_char fmt ',' in
let pp_list pp fmt list = Format.pp_print_list ~pp_sep pp fmt list in
let pp_contracts_gas fmt =
ExprMap.iter
(fun hash {script = _; addresses = _; storages} ->
let hash_string = P.Script.Hash.to_b58check hash in
ExprMap.iter
(fun _hash {contract; storage = _; gas} ->
match gas with
| None -> assert false
| Some gas ->
let gas_list = gas_to_list gas in
let total = List.fold_left ( + ) 0 gas_list in
Format.fprintf
fmt
"%a%a%s%a%a\n"
P.Contract.pp
contract
pp_sep
()
hash_string
pp_sep
()
(pp_list Format.pp_print_int)
(gas_list @ [total]))
storages)
contract_map
in
File_helpers.print_to_file
file
"%a\n%t"
(pp_list Format.pp_print_string)
(("address" :: "code_hash" :: gas_headers) @ ["TOTAL"])
pp_contracts_gas ;
print_endline "Done writing gas file."
let main ~output_dir ctxt ~head : unit tzresult Lwt.t =
let open Lwt_result_syntax in
let head_hash, head_level = Tezos_store.Store.Block.descriptor head in
Format.printf "Head is %a, level %ld\n%!" Block_hash.pp head_hash head_level ;
let predecessor_timestamp = Tezos_store.Store.Block.timestamp head in
let timestamp = Time.Protocol.add predecessor_timestamp 10000L in
print_endline "Preparing raw context..." ;
let* ctxt =
P.Context.prepare ~level:head_level ~predecessor_timestamp ~timestamp ctxt
in
print_endline "Listing addresses..." ;
let*! contract_map, _ =
P.Contract.fold
ctxt
~init:(ExprMap.empty, 0)
~f:Michelson_helpers.(add_expr_to_map ctxt)
in
print_endline "Listing addresses done." ;
let* lambda_map, lambda_ty_map, lambda_legacy_map =
if Config.collect_lambdas then (
let add_typed_expr hash expr ty_hash ty exprs =
ExprMap.update
hash
(fun existing ->
let type_map =
match existing with
| None -> ExprMap.empty
| Some (_expr, _from_unpack, type_map) -> type_map
in
Some (expr, false, ExprMap.add ty_hash ty type_map))
exprs
in
print_endline "Getting expressions from contracts..." ;
let* exprs =
ExprMap.fold_es
(fun hash {script; storages; _} exprs ->
let exprs =
ExprMap.add hash (script, false, ExprMap.empty) exprs
in
let*! {
storage_type;
storage_type_hash;
parsing_storage_type_cost = _;
parsing_toplevel_cost = _;
} =
Michelson_helpers.get_script_storage_type ctxt script
in
ExprMap.fold_es
(fun storage_hash {contract = _; storage; gas = _} exprs ->
return
@@ add_typed_expr
storage_hash
storage
storage_type_hash
storage_type
exprs)
storages
exprs)
contract_map
ExprMap.empty
in
print_endline "Listing big maps..." ;
let* exprs, _ =
P.Storage.fold
ctxt
~init:(Ok (exprs, 0))
~f:(fun id exprs_i ->
let* exprs, i = Lwt.return exprs_i in
let i = i + 1 in
if i mod 100 = 0 then Format.printf "%d@." i ;
let*! value_opt =
Storage_helpers.get_value
~what:"big map value type"
~getter:P.Storage.get
~pp:(fun fmt id -> Z.pp_print fmt (P.Storage.id_to_z id))
ctxt
id
in
match value_opt with
| Error `AlreadyWarned -> return (exprs, i)
| Ok value_type_expr ->
let ty_hash, ty, _parsing_cost =
Michelson_helpers.parse_ty ctxt value_type_expr
in
let+ exprs =
big_map_fold (ctxt, id) ~init:exprs ~f:(fun v exprs ->
let v_hash = Michelson_helpers.hash_expr v in
return @@ add_typed_expr v_hash v ty_hash ty exprs)
in
(exprs, i))
in
print_endline "Computing unpack transitive closure of expressions..." ;
let exprs = Michelson_helpers.unpack_transitive_closure exprs in
print_endline "Collecting unpack types..." ;
let unpack_types =
let parse_ty expr =
let hash, ty, _cost = Michelson_helpers.parse_ty ctxt expr in
(hash, ty)
in
Michelson_helpers.collect_unpack_types ~parse_ty exprs
in
print_endline "Collecting all lambdas..." ;
let*! lambda_map =
Michelson_helpers.collect_lambdas_in_exprs ctxt exprs unpack_types
in
return lambda_map)
else return (ExprMap.empty, ExprMap.empty, ExprMap.empty)
in
let* total_ir_size =
if Config.(print_contracts || measure_code_size || collect_storage) then (
print_endline "Writing contract files and measuring code size..." ;
let+ total_ir_size =
ExprMap.fold_es
(output_contract_results ctxt output_dir)
contract_map
Contract_size.zero
in
print_endline "Done writing contract files." ;
total_ir_size)
else return Contract_size.zero
in
if Config.measure_code_size then
Format.printf
"@[<v 2>Total IR size:@;%a@]@."
Contract_size.pp
total_ir_size ;
let () =
if not (ExprMap.is_empty lambda_map) then (
print_endline "Writing lambda files..." ;
let dirname = Filename.concat output_dir "lambdas" in
File_helpers.print_expr_dir ~dirname ~ext:".tz" lambda_map ;
File_helpers.print_expr_dir ~dirname ~ext:".ty" lambda_ty_map ;
File_helpers.print_legacy_dir
~dirname
~ext:".legacy_flag"
lambda_legacy_map ;
print_endline "Done writing lambda files.")
else ()
in
let () =
if Config.collect_gas then write_gas_file ~output_dir contract_map
in
return ()
end
let ensure_target_dir_exists list_target_dir =
let dir =
match list_target_dir with
| [] -> "."
| [dir] -> dir
| _ :: _ ->
Printf.printf "Got more than one target dir!" ;
exit 1
in
try
if Sys.is_directory dir then dir
else (
Printf.printf "Given path: %s points to a file; directory expected!" dir ;
exit 1)
with Sys_error _ -> (
Printf.printf "Directory %s does not exists. Create? [Y/n] " dir ;
let response = read_line () in
match response with
| "" | "y" | "Y" ->
mkdir dir ;
dir
| _ -> exit 0)
let get_main proto_hash =
match
List.find
(fun (module Proto : Sigs.PROTOCOL) ->
Protocol_hash.(Proto.hash = proto_hash))
(Known_protocols.get_all ())
with
| None ->
Format.kasprintf
invalid_arg
"Unknown protocol: %a"
Protocol_hash.pp
proto_hash
| Some (module Proto) ->
let module Main = Make (Proto) in
(module Main : Sigs.MAIN)
let data_dir_param = Tezos_clic.string ~name:"data-dir" ~desc:"Path to context"
let list_target_dir_param =
Tezos_clic.seq_of_param
@@ Tezos_clic.string ~name:"target-dir" ~desc:"Output path"
let network_parameter =
Tezos_clic.parameter (fun () network_name ->
match
List.assoc ~equal:String.equal network_name Config.known_networks
with
| None -> failwith "Unknown network name"
| Some n -> Lwt_result_syntax.return n)
let network_arg =
Tezos_clic.default_arg
~doc:"Network to use"
~long:"network"
~placeholder:"network name"
~default:"mainnet"
network_parameter
let commands =
[
command
~desc:"Extracts all contracts from the storage"
(args1 network_arg)
(data_dir_param @@ list_target_dir_param)
(fun genesis data_dir list_target_dir () ->
let open Lwt_result_syntax in
let output_dir = ensure_target_dir_exists list_target_dir in
Printf.printf "Initializing store from data dir '%s'...\n%!" data_dir ;
let* store =
Tezos_store.Store.init
~store_dir:(Filename.concat data_dir "store")
~context_dir:(Filename.concat data_dir "context")
~allow_testchains:true
~readonly:true
genesis
in
Printf.printf "Getting main chain storage and head...\n%!" ;
let chain_store = Tezos_store.Store.main_chain_store store in
let chain_id = Tezos_store.Store.Chain.chain_id chain_store in
Format.printf "Chain id: %a\n%!" Chain_id.pp chain_id ;
let*! head = Tezos_store.Store.Chain.current_head chain_store in
Format.printf
"Head block: %a\n%!"
Block_hash.pp
(Tezos_store.Store.Block.hash head) ;
let* proto_hash =
Tezos_store.Store.Block.protocol_hash chain_store head
in
Format.printf "Protocol hash: %a\n%!" Protocol_hash.pp proto_hash ;
let*! ctxt = Tezos_store.Store.Block.context_exn chain_store head in
print_endline "Pre-preparing raw context..." ;
let (module Main : Sigs.MAIN) = get_main proto_hash in
Main.main ~output_dir ctxt ~head);
]
let run () =
let argv = Sys.argv |> Array.to_list |> List.tl |> Option.value ~default:[] in
Tezos_clic.dispatch commands () argv
let () =
match Lwt_main.run (run ()) with
| Ok () -> ()
| Error trace -> Format.printf "ERROR: %a%!" Error_monad.pp_print_trace trace