defmodule Stellar.TxBuild.HostFunction do
@moduledoc """
`HostFunction` struct definition.
"""
alias Stellar.TxBuild.{ContractAuth, HostFunctionArgs}
alias StellarBase.XDR.ContractAuthList
alias StellarBase.XDR.ContractAuth, as: ContractAuthXDR
alias StellarBase.XDR.HostFunction, as: HostFunctionXDR
@behaviour Stellar.TxBuild.XDR
@type args :: HostFunctionArgs.t()
@type contract_auth :: ContractAuth.t()
@type contract_auth_xdr :: ContractAuthXDR.t()
@type auth :: list(contract_auth()) | list(String.t())
@type error :: {:error, atom()}
@type validation :: {:ok, any()} | error()
@type t :: %__MODULE__{
args: args(),
auth: auth()
}
defstruct [:args, :auth]
@impl true
def new(args, opts \\ [])
def new(args, _opts) when is_list(args) do
function_args = Keyword.get(args, :args)
auth = Keyword.get(args, :auth, [])
with {:ok, args} <- validate_host_function_args(function_args),
{:ok, auth} <- validate_host_function_auth(auth) do
%__MODULE__{args: args, auth: auth}
end
end
def new(_args, _opts), do: {:error, :invalid_operation_attributes}
@impl true
def to_xdr(%__MODULE__{
args: args,
auth: [%ContractAuth{} | _] = auth
})
when is_list(auth) do
args_xdr = HostFunctionArgs.to_xdr(args)
contract_auth =
auth
|> Enum.map(&ContractAuth.to_xdr/1)
|> ContractAuthList.new()
HostFunctionXDR.new(args_xdr, contract_auth)
end
def to_xdr(%__MODULE__{
args: args,
auth: auth
})
when is_list(auth) do
args_xdr = HostFunctionArgs.to_xdr(args)
contract_auth =
auth
|> Enum.map(&decode_contract_auth/1)
|> ContractAuthList.new()
HostFunctionXDR.new(args_xdr, contract_auth)
end
@spec set_auth(module :: t(), auth :: auth()) :: t() | error()
def set_auth(%__MODULE__{} = module, auth) when is_list(auth) do
with {:ok, auth} <- validate_auth_strings(auth) do
%{module | auth: auth}
end
end
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_function_args(args :: args()) :: validation()
defp validate_host_function_args(%HostFunctionArgs{} = args), do: {:ok, args}
defp validate_host_function_args(_args), do: {:error, :invalid_args}
@spec validate_host_function_auth(auth :: auth()) :: validation()
defp validate_host_function_auth(auth) do
if Enum.all?(auth, &is_contract_auth?/1),
do: {:ok, auth},
else: {:error, :invalid_auth}
end
@spec is_contract_auth?(contract_auth :: contract_auth()) :: boolean()
defp is_contract_auth?(%ContractAuth{}), do: true
defp is_contract_auth?(_arg), do: false
@spec decode_contract_auth(auth :: String.t()) :: contract_auth_xdr()
defp decode_contract_auth(auth) do
{contract_auth, ""} =
auth
|> Base.decode64!()
|> ContractAuthXDR.decode_xdr!()
contract_auth
end
end