lib/tx_build/host_function.ex

defmodule Stellar.TxBuild.HostFunction do
  @moduledoc """
    `HostFunction` struct definition.
  """
  alias Stellar.TxBuild.{CreateContractArgs, InvokeContractArgs}
  alias StellarBase.XDR.{HostFunction, HostFunctionType, VariableOpaque}

  @behaviour Stellar.TxBuild.XDR

  @type value :: CreateContractArgs.t() | InvokeContractArgs.t() | binary()
  @type error :: {:error, atom()}
  @type validation :: {:ok, any()} | error()
  @type type ::
          :invoke_contract
          | :create_contract
          | :upload_contract_wasm
  @type t :: %__MODULE__{
          type: type(),
          value: value()
        }

  defstruct [:type, :value]

  @allowed_types ~w(invoke_contract create_contract upload_contract_wasm)a

  @impl true
  def new(args, opts \\ [])

  def new([{type, value}], _opts) when type in @allowed_types do
    with {:ok, _value} <- validate_host_function({type, value}) do
      %__MODULE__{
        type: type,
        value: value
      }
    end
  end

  def new(_args, _opts), do: {:error, :invalid_operation_attributes}

  @impl true
  def to_xdr(%__MODULE__{
        type: :invoke_contract,
        value: value
      }) do
    type = HostFunctionType.new(:HOST_FUNCTION_TYPE_INVOKE_CONTRACT)

    value
    |> InvokeContractArgs.to_xdr()
    |> HostFunction.new(type)
  end

  def to_xdr(%__MODULE__{
        type: :create_contract,
        value: value
      }) do
    type = HostFunctionType.new(:HOST_FUNCTION_TYPE_CREATE_CONTRACT)

    value
    |> CreateContractArgs.to_xdr()
    |> HostFunction.new(type)
  end

  def to_xdr(%__MODULE__{
        type: :upload_contract_wasm,
        value: value
      }) do
    type = HostFunctionType.new(:HOST_FUNCTION_TYPE_UPLOAD_CONTRACT_WASM)

    value
    |> VariableOpaque.new()
    |> HostFunction.new(type)
  end

  @spec validate_host_function({type :: atom(), value :: value()}) :: validation()
  defp validate_host_function({:invoke_contract, %InvokeContractArgs{} = value}), do: {:ok, value}
  defp validate_host_function({:create_contract, %CreateContractArgs{} = value}), do: {:ok, value}

  defp validate_host_function({:upload_contract_wasm, value}) when is_binary(value),
    do: {:ok, value}

  defp validate_host_function({type, _value}), do: {:error, :"invalid_#{type}"}
end