defmodule Stellar.TxBuild.InvokeHostFunction do @moduledoc """ Performs the following operations: - Invokes contract functions. - Installs WASM of the new contracts. - Deploys new contracts using the installed WASM or built-in implementations. """ import Stellar.TxBuild.Validations, only: [validate_optional_account: 1] alias Stellar.TxBuild.{ContractAuth, HostFunction, OptionalAccount} alias StellarBase.XDR.Operations.InvokeHostFunction alias StellarBase.XDR.{ OperationBody, OperationType, LedgerFootprint, ContractAuthList, LedgerKeyList } @behaviour Stellar.TxBuild.XDR @type validation :: {:ok, any()} | {:error, atom()} @type t :: %__MODULE__{ function: HostFunction.t(), footprint: String.t(), auth: list(ContractAuth.t()), source_account: OptionalAccount.t() } defstruct [:function, :footprint, :auth, :source_account] @impl true def new(args, opts \\ []) def new(args, _opts) when is_list(args) do function = Keyword.get(args, :function) footprint = Keyword.get(args, :footprint) auth = Keyword.get(args, :auth) source_account = Keyword.get(args, :source_account) with {:ok, function} <- validate_function(function), {:ok, footprint} <- validate_footprint({:footprint, footprint}), {:ok, source_account} <- validate_optional_account({:source_account, source_account}), {:ok, auth} <- validate_auth(auth) do %__MODULE__{ function: function, footprint: footprint, auth: auth, source_account: source_account } end end def new(_args, _opts), do: {:error, :invalid_operation_attributes} @impl true def to_xdr(%__MODULE__{ function: function, footprint: nil, auth: nil }) do op_type = OperationType.new(:INVOKE_HOST_FUNCTION) host_function = HostFunction.to_xdr(function) ledger_key_list = LedgerKeyList.new([]) ledger_footprint = LedgerFootprint.new(ledger_key_list, ledger_key_list) contract_auth_list = ContractAuthList.new([]) host_function |> InvokeHostFunction.new(ledger_footprint, contract_auth_list) |> OperationBody.new(op_type) end def to_xdr(%__MODULE__{ function: function, footprint: nil, auth: auth }) do op_type = OperationType.new(:INVOKE_HOST_FUNCTION) host_function = HostFunction.to_xdr(function) ledger_key_list = LedgerKeyList.new([]) ledger_footprint = LedgerFootprint.new(ledger_key_list, ledger_key_list) contract_auth_list = auth |> Enum.map(&ContractAuth.to_xdr/1) |> ContractAuthList.new() host_function |> InvokeHostFunction.new(ledger_footprint, contract_auth_list) |> OperationBody.new(op_type) end def to_xdr(%__MODULE__{ function: function, footprint: footprint, auth: nil }) do op_type = OperationType.new(:INVOKE_HOST_FUNCTION) host_function = HostFunction.to_xdr(function) {ledger_footprint, _} = footprint |> Base.decode64!() |> LedgerFootprint.decode_xdr!() contract_auth_list = ContractAuthList.new([]) host_function |> InvokeHostFunction.new(ledger_footprint, contract_auth_list) |> OperationBody.new(op_type) end def to_xdr(%__MODULE__{ function: function, footprint: footprint, auth: auth }) do op_type = OperationType.new(:INVOKE_HOST_FUNCTION) host_function = HostFunction.to_xdr(function) {ledger_footprint, _} = footprint |> Base.decode64!() |> LedgerFootprint.decode_xdr!() contract_auth_list = auth |> Enum.map(&ContractAuth.to_xdr/1) |> ContractAuthList.new() host_function |> InvokeHostFunction.new(ledger_footprint, contract_auth_list) |> OperationBody.new(op_type) end @spec set_footprint(module :: t(), footprint :: String.t()) :: t() def set_footprint(%__MODULE__{} = module, footprint) do with {:ok, footprint} <- validate_footprint({:footprint, footprint}) do %{module | footprint: footprint} end end @spec validate_function(function :: HostFunction.t()) :: validation() defp validate_function(%HostFunction{} = function), do: {:ok, function} defp validate_function(_), do: {:error, :invalid_function} @spec validate_footprint(tuple :: tuple()) :: validation() defp validate_footprint({:footprint, nil}), do: {:ok, nil} defp validate_footprint({:footprint, footprint}) when is_binary(footprint) do case Base.decode64(footprint) do {:ok, _} -> {:ok, footprint} :error -> {:error, :invalid_footprint} end end defp validate_footprint({:footprint, _}), do: {:error, :invalid_footprint} @spec validate_auth(function :: list(ContractAuth.t())) :: validation() defp validate_auth([%ContractAuth{} | _] = auth), do: {:ok, auth} defp validate_auth(nil), do: {:ok, nil} defp validate_auth(_), do: {:error, :invalid_auth} end