https://gitlab.com/tezos/tezos
Raw File
Tip revision: d31b5c0208782ddc1d818db32fc9c4764c9e2e64 authored by François Thiré on 17 May 2022, 15:56:51 UTC
[skip-ci]
Tip revision: d31b5c0
client_proto_fa12_commands.ml
(*****************************************************************************)
(*                                                                           *)
(* Open Source License                                                       *)
(* Copyright (c) 2020 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 Protocol
open Alpha_context
open Client_proto_args

let group =
  {
    Clic.name = "tokens";
    title = "Commands for managing FA1.2-compatible smart contracts";
  }

let alias_param = Client_proto_contracts.ContractAlias.destination_param

let token_contract_param () =
  alias_param
    ~name:"contract"
    ~desc:"name or address of the FA1.2-compatible contract"

let from_param () =
  alias_param ~name:"from" ~desc:"name or address of the sender"

let to_param () = alias_param ~name:"to" ~desc:"name or address of the receiver"

let amount_param () =
  Clic.param
    ~name:"amount"
    ~desc:"number of tokens"
    (Clic.parameter (fun _ s ->
         try
           let v = Z.of_string s in
           assert (Compare.Z.(v >= Z.zero)) ;
           return v
         with _ -> failwith "invalid amount (must be a non-negative number)"))

let tez_amount_arg =
  tez_arg ~default:"0" ~parameter:"tez-amount" ~doc:"amount in \xEA\x9C\xA9"

let as_arg =
  Client_proto_contracts.ContractAlias.destination_arg
    ~name:"as"
    ~doc:"name or address of the caller of the contract"
    ()

let custom_gas_flag =
  Clic.arg
    ~long:"gas"
    ~short:'G'
    ~doc:"Initial quantity of gas for typechecking and execution"
    ~placeholder:"gas"
    (Clic.parameter (fun _ctx str ->
         try
           let v = Z.of_string str in
           assert (Compare.Z.(v >= Z.zero)) ;
           return (Alpha_context.Gas.Arith.integral_exn v)
         with _ -> failwith "invalid gas limit (must be a positive number)"))

let payer_arg =
  Client_proto_contracts.ContractAlias.destination_arg
    ~name:"payer"
    ~doc:"name of the payer (i.e. SOURCE) contract for the transaction"
    ()

let callback_entrypoint_arg =
  Clic.arg
    ~doc:"Entrypoint the view should use to callback to"
    ~long:"callback-entrypoint"
    ~placeholder:"name"
    string_parameter

let contract_call_options =
  Clic.args14
    tez_amount_arg
    fee_arg
    Client_proto_context_commands.dry_run_switch
    Client_proto_context_commands.verbose_signing_switch
    gas_limit_arg
    storage_limit_arg
    counter_arg
    no_print_source_flag
    minimal_fees_arg
    minimal_nanotez_per_byte_arg
    minimal_nanotez_per_gas_unit_arg
    force_low_fee_arg
    fee_cap_arg
    burn_cap_arg

let contract_view_options =
  Clic.args15
    callback_entrypoint_arg
    tez_amount_arg
    fee_arg
    Client_proto_context_commands.dry_run_switch
    Client_proto_context_commands.verbose_signing_switch
    gas_limit_arg
    storage_limit_arg
    counter_arg
    no_print_source_flag
    minimal_fees_arg
    minimal_nanotez_per_byte_arg
    minimal_nanotez_per_gas_unit_arg
    force_low_fee_arg
    fee_cap_arg
    burn_cap_arg

let originate_options =
  Clic.args16
    tez_amount_arg
    fee_arg
    Client_proto_context_commands.dry_run_switch
    Client_proto_context_commands.verbose_signing_switch
    gas_limit_arg
    storage_limit_arg
    delegate_arg
    (Client_keys.force_switch ())
    init_arg
    no_print_source_flag
    minimal_fees_arg
    minimal_nanotez_per_byte_arg
    minimal_nanotez_per_gas_unit_arg
    force_low_fee_arg
    fee_cap_arg
    burn_cap_arg

let view_options =
  Clic.args3 custom_gas_flag payer_arg (unparsing_mode_arg ~default:"Readable")

let dummy_callback = Contract.implicit_contract Signature.Public_key_hash.zero

let get_contract_caller_keys cctxt caller =
  match Contract.is_implicit caller with
  | None ->
      failwith "only implicit accounts can be the source of a contract call"
  | Some source ->
      Client_keys.get_key cctxt source >>=? fun (_, caller_pk, caller_sk) ->
      return (source, caller_pk, caller_sk)

let commands () : #Protocol_client_context.full Clic.command list =
  let open Client_proto_args in
  Clic.
    [
      command
        ~group
        ~desc:"Check that a contract is FA1.2-compatible."
        no_options
        (prefixes ["check"; "contract"]
        @@ token_contract_param ()
        @@ prefixes ["implements"; "fa1.2"]
        @@ stop)
        (fun () (_, contract) (cctxt : #Protocol_client_context.full) ->
          Client_proto_fa12.contract_has_fa12_interface
            cctxt
            ~chain:cctxt#chain
            ~block:cctxt#block
            ~contract
            ()
          >>=? fun _ ->
          Format.printf
            "Contract %a has an FA1.2 interface.\n%!"
            Contract.pp
            contract ;
          return_unit);
      command
        ~group
        ~desc:"Transfer tokens between two given accounts"
        (Clic.args15
           as_arg
           tez_amount_arg
           fee_arg
           Client_proto_context_commands.dry_run_switch
           Client_proto_context_commands.verbose_signing_switch
           gas_limit_arg
           storage_limit_arg
           counter_arg
           no_print_source_flag
           minimal_fees_arg
           minimal_nanotez_per_byte_arg
           minimal_nanotez_per_gas_unit_arg
           force_low_fee_arg
           fee_cap_arg
           burn_cap_arg)
        (prefixes ["from"; "fa1.2"; "contract"]
        @@ token_contract_param () @@ prefix "transfer" @@ amount_param ()
        @@ prefix "from" @@ from_param () @@ prefix "to" @@ to_param () @@ stop
        )
        (fun ( as_address,
               tez_amount,
               fee,
               dry_run,
               verbose_signing,
               gas_limit,
               storage_limit,
               counter,
               no_print_source,
               minimal_fees,
               minimal_nanotez_per_byte,
               minimal_nanotez_per_gas_unit,
               force_low_fee,
               fee_cap,
               burn_cap )
             (_, contract)
             amount
             src
             (_, dst)
             (cctxt : #Protocol_client_context.full) ->
          let _, caller = Option.value ~default:src as_address in
          get_contract_caller_keys cctxt caller
          >>=? fun (source, caller_pk, caller_sk) ->
          let action = Client_proto_fa12.Transfer (snd src, dst, amount) in
          let fee_parameter =
            {
              Injection.minimal_fees;
              minimal_nanotez_per_byte;
              minimal_nanotez_per_gas_unit;
              force_low_fee;
              fee_cap;
              burn_cap;
            }
          in
          Client_proto_fa12.call_contract
            cctxt
            ~chain:cctxt#chain
            ~block:cctxt#block
            ~contract
            ~action
            ?confirmations:cctxt#confirmations
            ~dry_run
            ~verbose_signing
            ?fee
            ~source
            ~src_pk:caller_pk
            ~src_sk:caller_sk
            ~tez_amount
            ?gas_limit
            ?storage_limit
            ?counter
            ~fee_parameter
            ()
          >>= Client_proto_context_commands.report_michelson_errors
                ~no_print_source
                ~msg:"transfer simulation failed"
                cctxt
          >>= fun _ -> return_unit);
      command
        ~group
        ~desc:"Allow account to transfer an amount of token"
        contract_call_options
        (prefixes ["from"; "fa1.2"; "contract"]
        @@ token_contract_param () @@ prefix "as"
        @@ alias_param ~name:"as" ~desc:"name or address of the sender"
        @@ prefix "approve" @@ amount_param () @@ prefix "from"
        @@ alias_param
             ~name:"from"
             ~desc:"name or address to approve withdrawal"
        @@ stop)
        (fun ( tez_amount,
               fee,
               dry_run,
               verbose_signing,
               gas_limit,
               storage_limit,
               counter,
               no_print_source,
               minimal_fees,
               minimal_nanotez_per_byte,
               minimal_nanotez_per_gas_unit,
               force_low_fee,
               fee_cap,
               burn_cap )
             (_, contract)
             (_, source)
             amount
             (_, dst)
             (cctxt : #Protocol_client_context.full) ->
          get_contract_caller_keys cctxt source
          >>=? fun (source, src_pk, src_sk) ->
          let action = Client_proto_fa12.Approve (dst, amount) in
          let fee_parameter =
            {
              Injection.minimal_fees;
              minimal_nanotez_per_byte;
              minimal_nanotez_per_gas_unit;
              force_low_fee;
              fee_cap;
              burn_cap;
            }
          in
          Client_proto_fa12.call_contract
            cctxt
            ~chain:cctxt#chain
            ~block:cctxt#block
            ~contract
            ~action
            ?confirmations:cctxt#confirmations
            ~dry_run
            ~verbose_signing
            ?fee
            ~source
            ~src_pk
            ~src_sk
            ~tez_amount
            ?gas_limit
            ?storage_limit
            ?counter
            ~fee_parameter
            ()
          >>= Client_proto_context_commands.report_michelson_errors
                ~no_print_source
                ~msg:"transfer simulation failed"
                cctxt
          >>= fun _ -> return_unit);
      command
        ~group
        ~desc:"Ask for an address's balance offchain"
        view_options
        (prefixes ["from"; "fa1.2"; "contract"]
        @@ token_contract_param ()
        @@ prefixes ["get"; "balance"; "for"]
        @@ alias_param
             ~name:"from"
             ~desc:
               "name or address of the account to lookup (also the source \
                contract)"
        @@ stop)
        (fun (gas, payer, unparsing_mode)
             (_, contract)
             (_, addr)
             (cctxt : #Protocol_client_context.full) ->
          let action =
            Client_proto_fa12.Get_balance (addr, (dummy_callback, None))
          in
          let payer = Option.map snd payer in
          Client_proto_fa12.run_view_action
            cctxt
            ~chain:cctxt#chain
            ~block:cctxt#block
            ~contract
            ~action
            ~source:addr
            ?gas
            ?payer
            ~unparsing_mode
            ()
          >>= fun res -> Client_proto_programs.print_view_result cctxt res);
      command
        ~group
        ~desc:"Ask for an address's allowance offchain"
        view_options
        (prefixes ["from"; "fa1.2"; "contract"]
        @@ token_contract_param ()
        @@ prefixes ["get"; "allowance"; "on"]
        @@ alias_param
             ~name:"owner"
             ~desc:"name or address of the account giving the allowance"
        @@ prefix "as"
        @@ alias_param
             ~name:"operator"
             ~desc:"name or address of the account receiving the allowance"
        @@ stop)
        (fun (gas, payer, unparsing_mode)
             (_, contract)
             (_, source)
             (_, destination)
             (cctxt : #Protocol_client_context.full) ->
          let action =
            Client_proto_fa12.Get_allowance
              (source, destination, (dummy_callback, None))
          in
          let payer = Option.map snd payer in
          Client_proto_fa12.run_view_action
            cctxt
            ~chain:cctxt#chain
            ~block:cctxt#block
            ~contract
            ~action
            ~source
            ?gas
            ?payer
            ~unparsing_mode
            ()
          >>= fun res -> Client_proto_programs.print_view_result cctxt res);
      command
        ~group
        ~desc:"Ask for the contract's total token supply offchain"
        view_options
        (prefixes ["from"; "fa1.2"; "contract"]
        @@ token_contract_param ()
        @@ prefixes ["get"; "total"; "supply"]
        @@ stop)
        (fun (gas, payer, unparsing_mode)
             (_, contract)
             (cctxt : #Protocol_client_context.full) ->
          let action =
            Client_proto_fa12.Get_total_supply (dummy_callback, None)
          in
          let payer = Option.map snd payer in
          Client_proto_fa12.run_view_action
            cctxt
            ~chain:cctxt#chain
            ~block:cctxt#block
            ~contract
            ~action
            ?gas
            ?payer
            ~unparsing_mode
            ()
          >>= fun res -> Client_proto_programs.print_view_result cctxt res);
      command
        ~group
        ~desc:"Ask for an address's balance using a callback contract"
        contract_view_options
        (prefixes ["from"; "fa1.2"; "contract"]
        @@ token_contract_param ()
        @@ prefixes ["get"; "balance"; "for"]
        @@ alias_param
             ~name:"from"
             ~desc:
               "name or address of the account to lookup (also the source \
                contract)"
        @@ prefixes ["callback"; "on"]
        @@ alias_param
             ~name:"callback"
             ~desc:"name or address of the callback contract"
        @@ stop)
        (fun ( callback_entrypoint,
               tez_amount,
               fee,
               dry_run,
               verbose_signing,
               gas_limit,
               storage_limit,
               counter,
               no_print_source,
               minimal_fees,
               minimal_nanotez_per_byte,
               minimal_nanotez_per_gas_unit,
               force_low_fee,
               fee_cap,
               burn_cap )
             (_, contract)
             (_, addr)
             (_, callback)
             (cctxt : #Protocol_client_context.full) ->
          get_contract_caller_keys cctxt addr
          >>=? fun (source, src_pk, src_sk) ->
          let action =
            Client_proto_fa12.Get_balance (addr, (callback, callback_entrypoint))
          in
          let fee_parameter =
            {
              Injection.minimal_fees;
              minimal_nanotez_per_byte;
              minimal_nanotez_per_gas_unit;
              force_low_fee;
              fee_cap;
              burn_cap;
            }
          in
          Client_proto_fa12.call_contract
            cctxt
            ~chain:cctxt#chain
            ~block:cctxt#block
            ~contract
            ~action
            ?confirmations:cctxt#confirmations
            ~dry_run
            ~verbose_signing
            ?fee
            ~source
            ~src_pk
            ~src_sk
            ~tez_amount
            ?gas_limit
            ?storage_limit
            ?counter
            ~fee_parameter
            ()
          >>= Client_proto_context_commands.report_michelson_errors
                ~no_print_source
                ~msg:"transfer simulation failed"
                cctxt
          >>= fun _ -> return_unit);
      command
        ~group
        ~desc:"Ask for an address's allowance using a callback contract"
        contract_view_options
        (prefixes ["from"; "fa1.2"; "contract"]
        @@ token_contract_param ()
        @@ prefixes ["get"; "allowance"; "on"]
        @@ alias_param
             ~name:"from"
             ~desc:"name or address of the account giving the allowance"
        @@ prefix "as"
        @@ alias_param
             ~name:"to"
             ~desc:"name or address of the account receiving the allowance"
        @@ prefixes ["callback"; "on"]
        @@ alias_param
             ~name:"callback"
             ~desc:"name or address of the callback contract"
        @@ stop)
        (fun ( callback_entrypoint,
               tez_amount,
               fee,
               dry_run,
               verbose_signing,
               gas_limit,
               storage_limit,
               counter,
               no_print_source,
               minimal_fees,
               minimal_nanotez_per_byte,
               minimal_nanotez_per_gas_unit,
               force_low_fee,
               fee_cap,
               burn_cap )
             (_, contract)
             (_, src)
             (_, dst)
             (_, callback)
             (cctxt : #Protocol_client_context.full) ->
          get_contract_caller_keys cctxt src
          >>=? fun (source, src_pk, src_sk) ->
          let action =
            Client_proto_fa12.Get_allowance
              (src, dst, (callback, callback_entrypoint))
          in
          let fee_parameter =
            {
              Injection.minimal_fees;
              minimal_nanotez_per_byte;
              minimal_nanotez_per_gas_unit;
              force_low_fee;
              fee_cap;
              burn_cap;
            }
          in
          Client_proto_fa12.call_contract
            cctxt
            ~chain:cctxt#chain
            ~block:cctxt#block
            ~contract
            ~action
            ?confirmations:cctxt#confirmations
            ~dry_run
            ~verbose_signing
            ?fee
            ~source
            ~src_pk
            ~src_sk
            ~tez_amount
            ?gas_limit
            ?storage_limit
            ?counter
            ~fee_parameter
            ()
          >>= Client_proto_context_commands.report_michelson_errors
                ~no_print_source
                ~msg:"transfer simulation failed"
                cctxt
          >>= fun _ -> return_unit);
      command
        ~group
        ~desc:
          "Ask for a contract's total token supply using a callback contract"
        contract_view_options
        (prefixes ["from"; "fa1.2"; "contract"]
        @@ token_contract_param ()
        @@ prefixes ["get"; "total"; "supply"; "as"]
        @@ alias_param
             ~name:"from"
             ~desc:"name or address of the source account"
        @@ prefixes ["callback"; "on"]
        @@ alias_param
             ~name:"callback"
             ~desc:"name or address of the callback contract"
        @@ stop)
        (fun ( callback_entrypoint,
               tez_amount,
               fee,
               dry_run,
               verbose_signing,
               gas_limit,
               storage_limit,
               counter,
               no_print_source,
               minimal_fees,
               minimal_nanotez_per_byte,
               minimal_nanotez_per_gas_unit,
               force_low_fee,
               fee_cap,
               burn_cap )
             (_, contract)
             (_, addr)
             (_, callback)
             (cctxt : #Protocol_client_context.full) ->
          get_contract_caller_keys cctxt addr
          >>=? fun (source, src_pk, src_sk) ->
          let action =
            Client_proto_fa12.Get_total_supply (callback, callback_entrypoint)
          in
          let fee_parameter =
            {
              Injection.minimal_fees;
              minimal_nanotez_per_byte;
              minimal_nanotez_per_gas_unit;
              force_low_fee;
              fee_cap;
              burn_cap;
            }
          in
          Client_proto_fa12.call_contract
            cctxt
            ~chain:cctxt#chain
            ~block:cctxt#block
            ~contract
            ~action
            ?confirmations:cctxt#confirmations
            ~dry_run
            ~verbose_signing
            ?fee
            ~source
            ~src_pk
            ~src_sk
            ~tez_amount
            ?gas_limit
            ?storage_limit
            ?counter
            ~fee_parameter
            ()
          >>= Client_proto_context_commands.report_michelson_errors
                ~no_print_source
                ~msg:"transfer simulation failed"
                cctxt
          >>= fun _ -> return_unit);
      command
        ~group
        ~desc:
          "Execute multiple token transfers from a single source account. If \
           one of the token transfers fails, none of them are executed."
        (args14
           default_fee_arg
           as_arg
           Client_proto_context_commands.dry_run_switch
           Client_proto_context_commands.verbose_signing_switch
           default_gas_limit_arg
           default_storage_limit_arg
           counter_arg
           no_print_source_flag
           minimal_fees_arg
           minimal_nanotez_per_byte_arg
           minimal_nanotez_per_gas_unit_arg
           force_low_fee_arg
           fee_cap_arg
           burn_cap_arg)
        (prefixes ["multiple"; "fa1.2"; "transfers"; "from"]
        @@ alias_param
             ~name:"src"
             ~desc:"name or address of the source of the transfers"
        @@ prefix "using"
        @@ param
             ~name:"transfers.json"
             ~desc:
               (Format.sprintf
                  "List of token transfers to inject from the source contract \
                   in JSON format (as a file or string). The JSON must be an \
                   array of objects of the form: '[ {\"token_contract\": \
                   address or alias, \"destination\": address or alias, \
                   \"amount\": non-negative integer (, <field>: <val> ...) } \
                   (, ...) ]', where an optional <field> can either be \
                   \"tez-amount\", \"fee\", \"gas-limit\" or \
                   \"storage-limit\". The complete schema can be inspected via \
                   `tezos-codec describe %s.fa1.2.token_transfer json schema`."
                  Protocol.name)
             Client_proto_context_commands.json_file_or_text_parameter
        @@ stop)
        (fun ( fee,
               as_address,
               dry_run,
               verbose_signing,
               gas_limit,
               storage_limit,
               counter,
               no_print_source,
               minimal_fees,
               minimal_nanotez_per_byte,
               minimal_nanotez_per_gas_unit,
               force_low_fee,
               fee_cap,
               burn_cap )
             src
             operations_json
             cctxt ->
          let _, caller = Option.value ~default:src as_address in
          let fee_parameter =
            {
              Injection.minimal_fees;
              minimal_nanotez_per_byte;
              minimal_nanotez_per_gas_unit;
              force_low_fee;
              fee_cap;
              burn_cap;
            }
          in
          match
            Data_encoding.Json.destruct
              (Data_encoding.list Client_proto_fa12.token_transfer_encoding)
              operations_json
          with
          | [] -> failwith "Empty operation list"
          | operations ->
              get_contract_caller_keys cctxt caller
              >>=? fun (source, src_pk, src_sk) ->
              Client_proto_fa12.inject_token_transfer_batch
                cctxt
                ~chain:cctxt#chain
                ~block:cctxt#block
                ?confirmations:cctxt#confirmations
                ~dry_run
                ~verbose_signing
                ~sender:(snd src)
                ~source
                ~src_pk
                ~src_sk
                ~token_transfers:operations
                ~fee_parameter
                ?counter
                ?default_fee:fee
                ?default_gas_limit:gas_limit
                ?default_storage_limit:storage_limit
                ()
              >>= Client_proto_context_commands.report_michelson_errors
                    ~no_print_source
                    ~msg:"multiple transfers simulation failed"
                    cctxt
              >>= fun _ -> return_unit
          | exception (Data_encoding.Json.Cannot_destruct (path, exn2) as exn)
            -> (
              match (path, operations_json) with
              | [`Index n], `A lj -> (
                  match List.nth_opt lj n with
                  | Some j ->
                      failwith
                        "Invalid transfer at index %i: %a %a"
                        n
                        (fun ppf -> Data_encoding.Json.print_error ppf)
                        exn2
                        Data_encoding.Json.pp
                        j
                  | _ ->
                      failwith
                        "Invalid transfer at index %i: %a"
                        n
                        (fun ppf -> Data_encoding.Json.print_error ppf)
                        exn2)
              | _ ->
                  failwith
                    "Invalid transfer file: %a %a"
                    (fun ppf -> Data_encoding.Json.print_error ppf)
                    exn
                    Data_encoding.Json.pp
                    operations_json));
    ]
back to top