Revision bf51bd7372b1703c825c607ffbacc7a79b6059bf authored by Arvid Jakobsson on 27 February 2020, 14:38:50 UTC, committed by Pierre Boutillier on 09 March 2020, 20:48:03 UTC
The latter was added in 9aebc957 but the former was not removed.
1 parent ede1919
Raw File
liquidity.ml
open Internal_pervasives
module MFmt = Experiments.Markup_fmt

let failf ?attach fmt =
  ksprintf (fun s -> fail ?attach (`Scenario_error s)) fmt

module Data = struct
  type t = Fmt of (Caml.Format.formatter -> unit)

  let to_string = function
    | Fmt f -> Caml.Format.asprintf "%a" (fun ppf () -> f ppf) ()

  let pp ppf (Fmt f) =
    let open Fmt in
    box (fun ppf () -> f ppf) ppf ()

  let rawf fmt =
    Caml.Format.kasprintf (fun s -> Fmt (fun ppf -> Fmt.string ppf s)) fmt

  let fmt f = Fmt f
  let address s = rawf "%s" s

  let list_like ~sep ~delimiter l =
    Fmt
      Fmt.(
        fun ppf ->
          delimiter
            (fun ppf () -> list ~sep (fun ppf (Fmt f) -> f ppf) ppf l)
            ppf ())

  let tuple l = list_like ~sep:Fmt.comma ~delimiter:Fmt.parens l
  let int i = Fmt (fun ppf -> Fmt.int ppf i)
  let string = rawf "%S"
  let nat d = rawf "%dp" d

  let tez (`Mutez d) =
    let million = 1_000_000. in
    rawf "%ftz" (Float.of_int d /. million)

  let semi_colon = Fmt.(fun ppf () -> string ppf ";" ; sp ppf ())
  let list l = list_like ~sep:semi_colon ~delimiter:Fmt.brackets l

  let set l =
    list_like ~sep:semi_colon l
      ~delimiter:Fmt.(fun x ppf -> pf ppf "@[<6>(Set [%a]@])" x)

  let empty_set = set []
  let key_hash s = rawf "%s" s
  let key s = rawf "%s" s
  let account_key a = Tezos_protocol.Account.pubkey a |> key
  let account_key_hash a = Tezos_protocol.Account.pubkey_hash a |> key_hash
  let signature s = rawf "%s" s
  let bytes s = rawf "0x%s" s
  let some s = fmt (fun ppf -> Fmt.pf ppf "@[<5>(Some %a)@]" pp s)
  let none = rawf "None"
  let typed_none t = rawf "(None : %s)" t

  let record l =
    list_like ~sep:semi_colon ~delimiter:Fmt.braces
      (List.map l ~f:(fun (k, v) ->
           fmt Fmt.(fun ppf -> pf ppf "@[<2>%s =@ %a@]" k pp v)))
end

module Contract = struct
  type t = {name: string; paths: string list; main_name: string option}

  let make ?(library = []) ?main_name name ~path =
    {name; paths= library @ [path]; main_name}

  let build_dir state t =
    Paths.root state // sprintf "liquidity-build-%s" t.name

  let ensure_build_dir state t =
    let dir = build_dir state t in
    Running_processes.run_successful_cmdf state "mkdir -p %s"
      (Caml.Filename.quote dir)
    >>= fun _ -> return dir

  let base_liquidity_command _state t =
    sprintf "liquidity %s %s"
      (List.map t.paths ~f:Caml.Filename.quote |> String.concat ~sep:" ")
      (Option.value_map t.main_name ~default:"" ~f:(sprintf "--main %s"))

  let michelson state t =
    let f = build_dir state t // sprintf "%s.tz" t.name in
    ensure_build_dir state t
    >>= fun _ ->
    Running_processes.run_successful_cmdf state "%s --no-annot -o %s"
      (base_liquidity_command state t)
      (Caml.Filename.quote f)
    >>= fun _ -> return f

  let storage_initialization state t ~tezos_node ~storage =
    ensure_build_dir state t
    >>= fun dir ->
    let out = dir // sprintf "%s-initial-storage.tz" t.name in
    Running_processes.run_successful_cmdf state
      "%s -o %s --tezos-node %s --init-storage %s"
      (base_liquidity_command state t)
      (Caml.Filename.quote out)
      (Caml.Filename.quote tezos_node)
      ( List.map storage ~f:(fun item ->
            Caml.Filename.quote (Data.to_string item))
      |> String.concat ~sep:" " )
    >>= fun _ -> System.read_file state out >>= fun content -> return content

  let arguments state t ~entry_point ~data =
    Running_processes.run_successful_cmdf state "%s --data %s %s"
      (base_liquidity_command state t)
      (Caml.Filename.quote entry_point)
      (Caml.Filename.quote (Data.to_string data))
    >>= fun res -> return (String.concat ~sep:" " res#out)

  let cmdliner_term ~prefix ~name () =
    let contract_name = name in
    let open Cmdliner in
    let open Term in
    let flag_name s = sprintf "%s-%s" prefix s in
    Arg.(
      pure (fun path main_name library ->
          make ~library ?main_name ~path contract_name)
      $ required
          (opt (some non_dir_file) None
             (info [flag_name "path"]
                ~doc:
                  (sprintf "Path to the liquidity %s contract." contract_name)))
      $ value
          (opt (some string) None
             (info [flag_name "main"]
                ~doc:
                  (sprintf "Name of “main” contract for %s." contract_name)))
      $ value
          (opt
             (list ~sep:',' non_dir_file)
             []
             (info [flag_name "library"]
                ~doc:
                  (sprintf
                     "Paths to extra liquidity %s contract-library files."
                     contract_name))))
end

module On_chain = struct
  let tezos_client_keyed_originate_contract ?(force = false)
      ?(transferring = 0) ?(burn_cap = 0.5) state keyed ~name ~source ~storage
      =
    let client = keyed.Tezos_client.Keyed.client in
    Tezos_client.successful_client_cmd state ~client
      ( [ "--wait"; "none"; "originate"; "contract"; name; "for"; keyed.key_name
        ; "transferring"; Int.to_string transferring; "from"; keyed.key_name
        ; "running"; source; "--init"; storage; "--burn-cap"
        ; Float.to_string burn_cap ]
      @ if force then ["--force"] else [] )

  let build_and_deploy ?(burn_cap = 10.1) state contract ~keyed_client ~storage
      ~balance =
    let name = contract.Contract.name in
    let tezos_node =
      sprintf "http://localhost:%d" keyed_client.Tezos_client.Keyed.client.port
    in
    Contract.michelson state contract
    >>= fun michetz ->
    Contract.storage_initialization state contract ~tezos_node
      ~storage:(List.map storage ~f:snd)
    >>= fun init ->
    tezos_client_keyed_originate_contract state keyed_client ~name
      ~transferring:balance ~source:michetz ~storage:init ~burn_cap ~force:true
    >>= fun _ ->
    Tezos_client.Keyed.bake state keyed_client (sprintf "%s origination" name)
    >>= fun () ->
    Tezos_client.successful_client_cmd state
      ~client:keyed_client.Tezos_client.Keyed.client
      ["show"; "known"; "contract"; name]
    >>= fun res ->
    let address = String.strip (String.concat ~sep:"" res#out) in
    Console.sayf state
      MFmt.(
        par (t "Deployed " @ hlf "“%s”" name)
        @ itemize [tf "Script: `%s`" michetz; tf "Address: `%s`" address]
        @ par (tf "Storage:")
        @ itemize
            (List.map storage ~f:(fun (name, data) ->
                 tf "%s:@ %a" name Data.pp data))
        |> to_fmt)
    >>= fun () -> return address

  (* This should go to flextesa soon... *)
  let silent_client_cmd state ~client args =
    Running_processes.run_cmdf state "sh -c %s"
      ( Tezos_client.client_command state client args
      |> Genspio.Compile.to_one_liner |> Caml.Filename.quote )
    >>= fun res ->
    let success = Poly.equal res#status (Lwt_unix.WEXITED 0) in
    return (success, res)

  let call ?msg ?(should = `Be_ok) ?(transferring = 0) ?(burn_cap = 0.3) state
      contract ~keyed_client ~entry_point ~data =
    Contract.arguments state contract ~entry_point ~data
    >>= fun low_level_arg ->
    silent_client_cmd state ~client:keyed_client.Tezos_client.Keyed.client
      [ "--wait"; "none"; "transfer"; Int.to_string transferring; "from"
      ; keyed_client.key_name; "to"; contract.name; "--burn-cap"
      ; Float.to_string burn_cap; "--arg"; low_level_arg ]
    >>= fun (succeeds, res) ->
    ( match succeeds with
    | false -> (
      match should with
      | `Fail -> return (`Expected `Failure)
      | `Script_failwith_re re ->
          let intersting_part =
            List.drop_while res#err ~f:(fun line ->
                String.is_prefix line ~prefix:"script reached FAILWITH")
            |> String.concat ~sep:" " in
          if Re.execp re intersting_part then return (`Expected `Failure)
          else return (`Failed `With_error_does_not_match)
      | `Command_stderr_re re ->
          if Re.execp re (String.concat ~sep:"\n" res#err) then
            return (`Expected `Failure)
          else return (`Failed `With_error_does_not_match)
      | `Be_ok -> return (`Failed `Not_ok) )
    | true when Poly.equal should `Be_ok ->
        silent_client_cmd state ~client:keyed_client.client
          [ "bake"; "for"; keyed_client.key_name; "--force"
          ; "--minimal-timestamp" ]
        >>= fun (_bake, _) -> return (`Expected `Ok)
    | true (* should is no ok *) -> return (`Failed `Unexpected_ok) )
    >>= fun test_status ->
    let test_full_name =
      sprintf "%s#%s%s" contract.name entry_point
        (Option.value_map msg ~default:"" ~f:(sprintf " (%s)")) in
    Console.sayf state
      MFmt.(
        let details =
          match test_status with
          | `Expected _ -> []
          | `Failed _ ->
              par (tf "Data:")
              @ verbatim [Data.to_string data]
              @ par (tf "Std-out:")
              @ verbatim res#out
              @ par (tf "Std-err:")
              @ verbatim res#err in
        par (tf "Test-call %s" test_full_name)
        @ itemize
            [ ( match test_status with
              | `Expected exp ->
                  hlf "Success: %s"
                    ( match exp with
                    | `Ok -> "op-baked"
                    | `Failure -> "expected-failure" )
              | `Failed reason ->
                  hlf "FAILURE: %s"
                    ( match reason with
                    | `Not_ok -> "Not-OK"
                    | `Unexpected_ok -> "Should-have-failed"
                    | `With_error_does_not_match ->
                        "Error-message-does-not-match" ) ) ]
        @ details
        |> to_fmt)
    >>= fun () ->
    match test_status with
    | `Expected _ -> return res
    | `Failed _ -> failf "Test failed: %s" test_full_name

  let key_with_type_json key =
    let open Ezjsonm in
    match key with
    | `Nat n ->
        ( dict [("int", `String (Int.to_string n))]
        , dict [("prim", `String "nat")] )

  let big_map_get state ~client ~address ~key =
    let post_json =
      let open Ezjsonm in
      let k, t = key_with_type_json key in
      dict [("key", k); ("type", t)] |> to_string ~minify:false in
    Tezos_client.rpc state ~client (`Post post_json)
      ~path:
        (sprintf "/chains/main/blocks/head/context/contracts/%s/big_map_get"
           address)
    >>= fun json ->
    return
      (object
         method post = post_json

         method result = json
      end)

  let show_contract_command state ~client ~name ~address ~pp_error =
    Console.Prompt.unit_and_loop
      ~description:(Fmt.str "Show status of the contract %s." address)
      [sprintf "show-%s" name] (fun _sexps ->
        Asynchronous_result.transform_error
          ~f:(fun e ->
            Caml.Format.kasprintf
              (fun s -> `Command_line s)
              "show-contract: %a" pp_error e)
          ( List.fold ["storage"; "balance"] ~init:(return [])
              ~f:(fun pm endpoint ->
                pm
                >>= fun l ->
                Tezos_client.rpc state ~client `Get
                  ~path:
                    (sprintf "/chains/main/blocks/head/context/contracts/%s/%s"
                       address endpoint)
                >>= fun json ->
                return
                  EF.(
                    desc (wf "/%s" endpoint)
                      (markdown_verbatim
                         (Ezjsonm.value_to_string ~minify:false json))
                    :: l))
          >>= fun l ->
          Console.say state
            EF.(
              desc
                (haf "Contract %s@%s" name address)
                (list ~sep:"" ~delimiters:("", "") l)) ))

  let big_map_get_command state ~names ~thing ~client ~name ~address
      ~key_of_string ~pp_error =
    Console.Prompt.unit_and_loop
      ~description:
        (Fmt.str "Get %s from the big-map of the contract %s@%s." thing name
           address) names (fun sexps ->
        Asynchronous_result.transform_error
          ~f:(fun e ->
            Caml.Format.kasprintf
              (fun s -> `Command_line s)
              "%s: %a" (List.hd_exn names) pp_error e)
          ( match sexps with
          | [Sexplib.Sexp.Atom s] ->
              key_of_string s
              >>= fun key ->
              big_map_get state ~client ~address ~key
              >>= fun getthing ->
              Console.sayf state
                MFmt.(
                  par (tf "Posted:")
                  @ verbatim_raw getthing#post
                  @ par (tf "Got:")
                  @ verbatim_ezjson getthing#result
                  |> to_fmt)
          | _ -> failf "Wrong s-exp command line" ))
end
back to top