defmodule Stellar.TxBuild.InvokeHostFunction do
@moduledoc """
Performs the following operations:
- Invokes contract host_functions.
- Upload WASM of the new contracts.
- Deploys new contracts using the uploaded WASM or built-in implementations.
"""
import Stellar.TxBuild.Validations, only: [validate_optional_account: 1]
alias Stellar.TxBuild.{HostFunction, SorobanAuthorizationEntry, OptionalAccount}
alias StellarBase.XDR.Operations.InvokeHostFunction
alias StellarBase.XDR.{
OperationBody,
OperationType,
SorobanAuthorizationEntryList
}
alias StellarBase.XDR.SorobanAuthorizationEntry, as: SorobanAuthorizationEntryXDR
@behaviour Stellar.TxBuild.XDR
@type auths :: list(SorobanAuthorizationEntry.t()) | list(String.t())
@type error :: {:error, atom()}
@type host_function :: HostFunction.t()
@type soroban_auth_xdr :: SorobanAuthorizationEntryXDR.t()
@type validation :: {:ok, any()} | {:error, atom()}
@type t :: %__MODULE__{
host_function: host_function(),
auths: auths(),
source_account: OptionalAccount.t()
}
defstruct [:host_function, :auths, :source_account]
@impl true
def new(host_function, opts \\ [])
def new(args, _opts) when is_list(args) do
host_function = Keyword.get(args, :host_function)
auths = Keyword.get(args, :auths, [])
source_account = Keyword.get(args, :source_account)
with {:ok, host_function} <- validate_host_host_function(host_function),
{:ok, auths} <- validate_soroban_auth_entries(auths),
{:ok, source_account} <- validate_optional_account({:source_account, source_account}) do
%__MODULE__{
host_function: host_function,
auths: auths,
source_account: source_account
}
end
end
def new(_args, _opts), do: {:error, :invalid_operation_attributes}
@impl true
def to_xdr(%__MODULE__{
host_function: host_function,
auths: [%SorobanAuthorizationEntry{} | _] = auths
}) do
op_type = OperationType.new(:INVOKE_HOST_FUNCTION)
auth =
auths
|> Enum.map(&SorobanAuthorizationEntry.to_xdr/1)
|> SorobanAuthorizationEntryList.new()
host_function
|> HostFunction.to_xdr()
|> InvokeHostFunction.new(auth)
|> OperationBody.new(op_type)
end
def to_xdr(%__MODULE__{
host_function: host_function,
auths: auths
})
when is_list(auths) do
op_type = OperationType.new(:INVOKE_HOST_FUNCTION)
auth =
auths
|> Enum.map(&decode_soroban_auth/1)
|> SorobanAuthorizationEntryList.new()
host_function
|> HostFunction.to_xdr()
|> InvokeHostFunction.new(auth)
|> OperationBody.new(op_type)
end
@spec set_auth(module :: t(), auths :: auths()) :: t() | error()
def set_auth(%__MODULE__{} = module, auths) when is_list(auths) do
with {:ok, auths} <- validate_auth_strings(auths) do
%{module | auths: auths}
end
end
def set_auth(_module, _auths), do: {:error, :invalid_auth}
defp validate_auth_strings(auth) do
if Enum.all?(auth, &validate_xdr_string/1),
do: {:ok, auth},
else: {:error, :invalid_auth}
end
@spec validate_xdr_string(xdr :: String.t() | nil) :: boolean()
defp validate_xdr_string(nil), do: true
defp validate_xdr_string(xdr) when is_binary(xdr) do
case Base.decode64(xdr) do
{:ok, _} -> true
:error -> false
end
end
@spec validate_host_host_function(host_function :: host_function()) :: validation()
defp validate_host_host_function(%HostFunction{} = host_function), do: {:ok, host_function}
defp validate_host_host_function(_host_function), do: {:error, :invalid_host_host_function}
defp validate_soroban_auth_entries(auths) when is_list(auths) do
if Enum.all?(auths, &is_soroban_auth_entry?/1),
do: {:ok, auths},
else: {:error, :invalid_soroban_auth_entries}
end
defp validate_soroban_auth_entries(_auths), do: {:error, :invalid_soroban_auth_entries}
defp is_soroban_auth_entry?(%SorobanAuthorizationEntry{}), do: true
defp is_soroban_auth_entry?(_auth), do: false
@spec decode_soroban_auth(auth :: String.t()) :: soroban_auth_xdr()
defp decode_soroban_auth(auth) do
{contract_auth, ""} =
auth
|> Base.decode64!()
|> SorobanAuthorizationEntryXDR.decode_xdr!()
contract_auth
end
end