defmodule Stellar.TxBuild.HostFunctionArgs do @moduledoc """ `HostFunctionArgs` struct definition. """ import Stellar.TxBuild.Validations, only: [ validate_sc_vals: 1, validate_contract_id: 1, validate_string: 1 ] alias Stellar.TxBuild.{Asset, SCVal, SCContractExecutable} alias StellarBase.XDR.HostFunctionArgs, as: HostFunctionArgsXDR alias StellarBase.XDR.{ ContractID, ContractIDType, CreateContractArgs, SCVec, HostFunctionType, UInt256, UploadContractWasmArgs, VariableOpaque256000 } @behaviour Stellar.TxBuild.XDR @type type :: :invoke | :upload | :create @type contract_id :: String.t() @type function_name :: String.t() @type invoke_args :: list(SCVal.t()) @type args :: invoke_args() @type wasm_id :: binary() @type asset :: Asset.t() @type error :: {:error, atom()} @type validation :: {:ok, any()} | error() @type t :: %__MODULE__{ type: type(), contract_id: contract_id(), function_name: function_name(), args: args(), code: binary(), wasm_id: wasm_id(), salt: binary(), asset: asset() } defstruct [:type, :contract_id, :function_name, :args, :code, :asset, :wasm_id, :salt] @impl true def new(args, opts \\ []) def new( [ {:type, :invoke}, {:contract_id, contract_id}, {:function_name, function_name}, {:args, args} ], _opts ) when is_list(args) do with {:ok, contract_id} <- validate_contract_id({:contract_id, contract_id}), {:ok, function_name} <- validate_string({:function_name, function_name}), {:ok, args} <- validate_sc_vals({:args, args}) do %__MODULE__{ type: :invoke, contract_id: contract_id, function_name: function_name, args: args } end end def new( [ {:type, :upload}, {:code, code} ], _opts ) when is_binary(code) do %__MODULE__{ type: :upload, code: code } end def new( [ {:type, :create}, {:wasm_id, wasm_id} ], _opts ) do new(type: :create, wasm_id: wasm_id, salt: :crypto.strong_rand_bytes(32)) end def new( [ {:type, :create}, {:wasm_id, wasm_id}, {:salt, salt} ], _opts ) do with {:ok, wasm_id} <- validate_wasm_id(wasm_id), {:ok, salt} <- validate_salt(salt) do %__MODULE__{ type: :create, wasm_id: wasm_id, salt: salt } end end def new( [ {:type, :create}, {:asset, asset} ], _opts ) do with {:ok, _asset} <- validate_asset(asset) do %__MODULE__{ type: :create, asset: asset } end end def new(_args, _opts), do: {:error, :invalid_operation_attributes} @impl true def to_xdr(%__MODULE__{ type: :invoke, contract_id: contract_id, function_name: function_name, args: args }) do contract_id_scval = contract_id |> Base.decode16!(case: :lower) |> (&SCVal.new(bytes: &1)).() |> SCVal.to_xdr() sc_symbol_scval = function_name |> (&SCVal.new(symbol: &1)).() |> SCVal.to_xdr() args_scvalxdr = Enum.map(args, &SCVal.to_xdr/1) sc_vec = [contract_id_scval, sc_symbol_scval] |> Kernel.++(args_scvalxdr) |> SCVec.new() host_function_type = HostFunctionType.new() HostFunctionArgsXDR.new(sc_vec, host_function_type) end def to_xdr(%__MODULE__{ type: :upload, code: code }) do host_function_type = HostFunctionType.new(:HOST_FUNCTION_TYPE_UPLOAD_CONTRACT_WASM) code |> VariableOpaque256000.new() |> UploadContractWasmArgs.new() |> HostFunctionArgsXDR.new(host_function_type) end def to_xdr(%__MODULE__{ type: :create, wasm_id: wasm_id, salt: salt, asset: nil }) do host_function_type = HostFunctionType.new(:HOST_FUNCTION_TYPE_CREATE_CONTRACT) contract_id_type = ContractIDType.new(:CONTRACT_ID_FROM_SOURCE_ACCOUNT) contract_id = salt |> UInt256.new() |> ContractID.new(contract_id_type) sc_contract_executable = [wasm_ref: wasm_id] |> SCContractExecutable.new() |> SCContractExecutable.to_xdr() contract_id |> CreateContractArgs.new(sc_contract_executable) |> HostFunctionArgsXDR.new(host_function_type) end def to_xdr(%__MODULE__{ type: :create, asset: asset }) do host_function_type = HostFunctionType.new(:HOST_FUNCTION_TYPE_CREATE_CONTRACT) contract_id_type = ContractIDType.new(:CONTRACT_ID_FROM_ASSET) contract_id = asset |> Asset.to_xdr() |> ContractID.new(contract_id_type) sc_contract_executable = :token |> SCContractExecutable.new() |> SCContractExecutable.to_xdr() contract_id |> CreateContractArgs.new(sc_contract_executable) |> HostFunctionArgsXDR.new(host_function_type) end @spec validate_asset(asset :: asset()) :: validation() defp validate_asset(%Asset{} = asset), do: {:ok, asset} defp validate_asset(_asset), do: {:error, :invalid_asset} @spec validate_salt(salt :: binary()) :: validation() defp validate_salt(salt) when is_binary(salt) and byte_size(salt) == 32, do: {:ok, salt} defp validate_salt(_salt), do: {:error, :invalid_salt} @spec validate_wasm_id(wasm_id :: binary()) :: validation() defp validate_wasm_id(wasm_id) when is_binary(wasm_id), do: {:ok, wasm_id} defp validate_wasm_id(_wasm_id), do: {:error, :invalid_wasm_id} end