defmodule Stellar.TxBuild.SCVal do
@moduledoc """
`SCVal` struct definition.
"""
@behaviour Stellar.TxBuild.XDR
alias Stellar.TxBuild.{SCAddress, SCMapEntry, SCStatus}
alias StellarBase.XDR.{
Bool,
Duration,
Hash,
Int128Parts,
Int32,
Int64,
SCBytes,
SCContractExecutable,
SCContractExecutableType,
SCMap,
SCNonceKey,
SCString,
SCSymbol,
SCVal,
SCValType,
SCVec,
OptionalSCMap,
OptionalSCVec,
TimePoint,
UInt32,
UInt64,
UInt256,
Void
}
@type type ::
:bool
| :void
| :status
| :u32
| :i32
| :u64
| :i64
| :time_point
| :duration
| :u128
| :i128
| :u256
| :i256
| :bytes
| :string
| :symbol
| :vec
| :map
| :contract
| :address
| :ledger_key_contract
| :ledger_key_nonce
@type validation :: {:ok, any()} | {:error, atom()}
@type value ::
nil
| integer()
| boolean()
| SCStatus.t()
| map()
| binary()
| list()
| atom()
| SCAddress.t()
@type t :: %__MODULE__{
type: type(),
value: value()
}
defstruct [:type, :value]
@impl true
def new(args, opts \\ nil)
def new([{type, value}], _opts)
when type in ~w(bool void status u32 i32 u64 i64 time_point duration u128 i128 u256 i256 bytes string symbol vec map contract address ledger_key_contract ledger_key_nonce)a do
with {:ok, _value} <- validate_sc_val({type, value}) do
%__MODULE__{
type: type,
value: value
}
end
end
def new(_args, _opts), do: {:error, :invalid_sc_val_type}
@impl true
def to_xdr(%__MODULE__{type: :bool, value: value}) do
type = SCValType.new(:SCV_BOOL)
value
|> Bool.new()
|> SCVal.new(type)
end
def to_xdr(%__MODULE__{type: :void, value: _value}) do
type = SCValType.new(:SCV_VOID)
Void.new()
|> SCVal.new(type)
end
def to_xdr(%__MODULE__{type: :status, value: value}) do
val_type = SCValType.new(:SCV_STATUS)
value
|> SCStatus.to_xdr()
|> SCVal.new(val_type)
end
def to_xdr(%__MODULE__{type: :u32, value: value}) do
type = SCValType.new(:SCV_U32)
value
|> UInt32.new()
|> SCVal.new(type)
end
def to_xdr(%__MODULE__{type: :i32, value: value}) do
type = SCValType.new(:SCV_I32)
value
|> Int32.new()
|> SCVal.new(type)
end
def to_xdr(%__MODULE__{type: :u64, value: value}) do
type = SCValType.new(:SCV_U64)
value
|> UInt64.new()
|> SCVal.new(type)
end
def to_xdr(%__MODULE__{type: :i64, value: value}) do
type = SCValType.new(:SCV_I64)
value
|> Int64.new()
|> SCVal.new(type)
end
def to_xdr(%__MODULE__{type: :time_point, value: value}) do
type = SCValType.new(:SCV_TIMEPOINT)
value
|> TimePoint.new()
|> SCVal.new(type)
end
def to_xdr(%__MODULE__{type: :duration, value: value}) do
type = SCValType.new(:SCV_DURATION)
value
|> Duration.new()
|> SCVal.new(type)
end
def to_xdr(%__MODULE__{type: :u128, value: %{lo: lo, hi: hi}}) do
type = SCValType.new(:SCV_U128)
lo = UInt64.new(lo)
hi = UInt64.new(hi)
lo
|> Int128Parts.new(hi)
|> SCVal.new(type)
end
def to_xdr(%__MODULE__{type: :i128, value: %{lo: lo, hi: hi}}) do
type = SCValType.new(:SCV_I128)
lo = UInt64.new(lo)
hi = UInt64.new(hi)
lo
|> Int128Parts.new(hi)
|> SCVal.new(type)
end
def to_xdr(%__MODULE__{type: :u256, value: value}) do
type = SCValType.new(:SCV_U256)
value
|> UInt256.new()
|> SCVal.new(type)
end
def to_xdr(%__MODULE__{type: :i256, value: value}) do
type = SCValType.new(:SCV_I256)
value
|> UInt256.new()
|> SCVal.new(type)
end
def to_xdr(%__MODULE__{type: :bytes, value: value}) do
type = SCValType.new(:SCV_BYTES)
value
|> SCBytes.new()
|> SCVal.new(type)
end
def to_xdr(%__MODULE__{type: :string, value: value}) do
type = SCValType.new(:SCV_STRING)
value
|> SCString.new()
|> SCVal.new(type)
end
def to_xdr(%__MODULE__{type: :symbol, value: value}) do
type = SCValType.new(:SCV_SYMBOL)
value
|> SCSymbol.new()
|> SCVal.new(type)
end
def to_xdr(%__MODULE__{type: :vec, value: nil}) do
type = SCValType.new(:SCV_VEC)
nil
|> OptionalSCVec.new()
|> SCVal.new(type)
end
def to_xdr(%__MODULE__{type: :vec, value: value}) do
type = SCValType.new(:SCV_VEC)
value
|> Enum.map(&to_xdr/1)
|> SCVec.new()
|> OptionalSCVec.new()
|> SCVal.new(type)
end
def to_xdr(%__MODULE__{type: :map, value: nil}) do
type = SCValType.new(:SCV_MAP)
nil
|> OptionalSCMap.new()
|> SCVal.new(type)
end
def to_xdr(%__MODULE__{type: :map, value: value}) do
type = SCValType.new(:SCV_MAP)
value
|> Enum.map(&SCMapEntry.to_xdr/1)
|> SCMap.new()
|> OptionalSCMap.new()
|> SCVal.new(type)
end
def to_xdr(%__MODULE__{type: :contract, value: {:wasm_ref, hash}}) do
type = SCValType.new(:SCV_CONTRACT_EXECUTABLE)
contract_code = SCContractExecutableType.new(:SCCONTRACT_EXECUTABLE_WASM_REF)
hash
|> Hash.new()
|> SCContractExecutable.new(contract_code)
|> SCVal.new(type)
end
def to_xdr(%__MODULE__{type: :contract, value: :token}) do
type = SCValType.new(:SCV_CONTRACT_EXECUTABLE)
contract_code = SCContractExecutableType.new(:SCCONTRACT_EXECUTABLE_TOKEN)
Void.new()
|> SCContractExecutable.new(contract_code)
|> SCVal.new(type)
end
def to_xdr(%__MODULE__{type: :address, value: value}) do
type = SCValType.new(:SCV_ADDRESS)
value
|> SCAddress.to_xdr()
|> SCVal.new(type)
end
def to_xdr(%__MODULE__{type: :ledger_key_contract, value: _value}) do
type = SCValType.new(:SCV_LEDGER_KEY_CONTRACT_EXECUTABLE)
Void.new()
|> SCVal.new(type)
end
def to_xdr(%__MODULE__{type: :ledger_key_nonce, value: value}) do
type = SCValType.new(:SCV_LEDGER_KEY_NONCE)
value
|> SCAddress.to_xdr()
|> SCNonceKey.new()
|> SCVal.new(type)
end
@spec validate_sc_val(tuple :: tuple()) :: validation()
defp validate_sc_val({type, value})
when type in ~w(u32 u64 time_point duration)a and is_integer(value) and value >= 0,
do: {:ok, value}
defp validate_sc_val({type, value})
when type in ~w(i32 i64)a and is_integer(value),
do: {:ok, value}
defp validate_sc_val({:bool, value}) when is_boolean(value), do: {:ok, value}
defp validate_sc_val({:void, _value}), do: {:ok, nil}
defp validate_sc_val({:status, %SCStatus{} = value}), do: {:ok, value}
defp validate_sc_val({type, %{lo: lo, hi: hi} = value})
when type in ~w(u128 i128)a and is_integer(lo) and is_integer(hi),
do: {:ok, value}
defp validate_sc_val({type, value})
when type in ~w(u256 i256 bytes string symbol)a and is_binary(value),
do: {:ok, value}
defp validate_sc_val({:vec, nil}), do: {:ok, nil}
defp validate_sc_val({:vec, value}) when is_list(value) do
if Enum.all?(value, &is_sc_val?/1),
do: {:ok, value},
else: {:error, :invalid_vec}
end
defp validate_sc_val({:map, nil}), do: {:ok, nil}
defp validate_sc_val({:map, value}) when is_list(value) do
if Enum.all?(value, &is_map_entry?/1),
do: {:ok, value},
else: {:error, :invalid_map}
end
defp validate_sc_val({:contract, :token}), do: {:ok, :token}
defp validate_sc_val({:contract, {:wasm_ref, hash} = value}) when is_binary(hash),
do: {:ok, value}
defp validate_sc_val({:address, %SCAddress{} = value}), do: {:ok, value}
defp validate_sc_val({:ledger_key_contract, _value}), do: {:ok, nil}
defp validate_sc_val({:ledger_key_nonce, %SCAddress{} = value}), do: {:ok, value}
defp validate_sc_val({type, _value}), do: {:error, :"invalid_#{type}"}
@spec is_map_entry?(value :: any()) :: boolean()
defp is_map_entry?(%SCMapEntry{}), do: true
defp is_map_entry?(_), do: false
@spec is_sc_val?(value :: any()) :: boolean()
defp is_sc_val?(%__MODULE__{}), do: true
defp is_sc_val?(_), do: false
end