defmodule Stellar.TxBuild.SCVal do
@moduledoc """
`SCVal` struct definition.
"""
import Bitwise
@behaviour Stellar.TxBuild.XDR
alias StellarBase.XDR.SCContractInstance
alias Stellar.TxBuild.{SCAddress, SCError, SCMapEntry}
alias StellarBase.XDR.{
Bool,
Duration,
Hash,
Int128Parts,
Int256Parts,
Int32,
Int64,
SCBytes,
ContractExecutable,
ContractExecutableType,
SCMap,
SCNonceKey,
SCString,
SCSymbol,
SCVal,
SCValType,
SCVec,
OptionalSCMap,
OptionalSCVec,
TimePoint,
UInt128Parts,
UInt256Parts,
UInt32,
UInt64,
Void
}
@type type ::
:bool
| :void
| :error
| :u32
| :i32
| :u64
| :i64
| :time_point
| :duration
| :u128
| :i128
| :u256
| :i256
| :bytes
| :string
| :symbol
| :vec
| :map
| :address
| :ledger_key_contract_instance
| :ledger_key_nonce
| :contract_instance
@type validation :: {:ok, any()} | {:error, atom()}
@type value ::
nil
| integer()
| boolean()
| SCError.t()
| map()
| binary()
| list()
| atom()
| SCAddress.t()
@type map_entry :: StellarBase.XDR.SCMapEntry.t()
@type t :: %__MODULE__{
type: type(),
value: value()
}
defstruct [:type, :value]
@allowed_types ~w(bool void error u32 i32 u64 i64 time_point duration u128 i128 u256 i256 bytes string symbol vec map address ledger_key_contract_instance ledger_key_nonce contract_instance)a
@impl true
def new(args, opts \\ nil)
def new([{type, value}], _opts)
when type in @allowed_types 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: :error, value: value}) do
val_type = SCValType.new(:SCV_ERROR)
value
|> SCError.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)
hi = UInt64.new(hi)
lo = UInt64.new(lo)
hi
|> UInt128Parts.new(lo)
|> SCVal.new(type)
end
def to_xdr(%__MODULE__{type: :i128, value: %{lo: lo, hi: hi}}) do
type = SCValType.new(:SCV_I128)
hi = Int64.new(hi)
lo = UInt64.new(lo)
hi
|> Int128Parts.new(lo)
|> SCVal.new(type)
end
def to_xdr(%__MODULE__{
type: :u256,
value: %{hi_hi: hi_hi, hi_lo: hi_lo, lo_hi: lo_hi, lo_lo: lo_lo}
}) do
type = SCValType.new(:SCV_U256)
hi_hi = UInt64.new(hi_hi)
hi_lo = UInt64.new(hi_lo)
lo_hi = UInt64.new(lo_hi)
lo_lo = UInt64.new(lo_lo)
hi_hi
|> UInt256Parts.new(hi_lo, lo_hi, lo_lo)
|> SCVal.new(type)
end
def to_xdr(%__MODULE__{
type: :i256,
value: %{hi_hi: hi_hi, hi_lo: hi_lo, lo_hi: lo_hi, lo_lo: lo_lo}
}) do
type = SCValType.new(:SCV_I256)
hi_hi = Int64.new(hi_hi)
hi_lo = UInt64.new(hi_lo)
lo_hi = UInt64.new(lo_hi)
lo_lo = UInt64.new(lo_lo)
hi_hi
|> Int256Parts.new(hi_lo, lo_hi, lo_lo)
|> 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: :ledger_key_contract_instance, value: nil}) do
type = SCValType.new(:SCV_LEDGER_KEY_CONTRACT_INSTANCE)
SCVal.new(Void.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: :contract_instance, value: {:wasm_ref, hash}}) do
type = SCValType.new(:SCV_CONTRACT_INSTANCE)
contract_code = ContractExecutableType.new(:CONTRACT_EXECUTABLE_WASM)
hash
|> Hash.new()
|> ContractExecutable.new(contract_code)
|> SCContractInstance.new(OptionalSCMap.new())
|> SCVal.new(type)
end
def to_xdr(%__MODULE__{type: :contract_instance, value: :token}) do
type = SCValType.new(:SCV_CONTRACT_INSTANCE)
contract_code = ContractExecutableType.new(:CONTRACT_EXECUTABLE_STELLAR_ASSET)
Void.new()
|> ContractExecutable.new(contract_code)
|> SCContractInstance.new(OptionalSCMap.new())
|> SCVal.new(type)
end
def to_xdr(%__MODULE__{type: :ledger_key_nonce, value: value}) do
type = SCValType.new(:SCV_LEDGER_KEY_NONCE)
value
|> Int64.new()
|> SCNonceKey.new()
|> SCVal.new(type)
end
@spec to_native_from_xdr(xdr :: String.t()) :: any
def to_native_from_xdr(xdr) when is_binary(xdr) do
with {:ok, {scval, _rest}} <- validate_xdr_decoding(xdr) do
to_native(scval)
end
end
def to_native_from_xdr(_xdr), do: {:error, :invalid_xdr}
@spec to_native(SCVal.t()) :: any
def to_native(%SCVal{
value: %StellarBase.XDR.SCSymbol{value: value},
type: %StellarBase.XDR.SCValType{identifier: :SCV_SYMBOL}
}) do
value
end
def to_native(%SCVal{
type: %StellarBase.XDR.SCValType{identifier: :SCV_VEC},
value: %StellarBase.XDR.OptionalSCVec{
sc_vec: %StellarBase.XDR.SCVec{
items: items
}
}
}) do
Enum.map(items, &to_native/1)
end
def to_native(%SCVal{
type: %StellarBase.XDR.SCValType{identifier: :SCV_VEC},
value: %StellarBase.XDR.OptionalSCVec{
sc_vec: nil
}
}) do
[]
end
def to_native(%SCVal{
type: %StellarBase.XDR.SCValType{identifier: :SCV_VOID},
value: _value
}) do
nil
end
def to_native(%SCVal{
type: %StellarBase.XDR.SCValType{identifier: :SCV_BOOL},
value: %StellarBase.XDR.Bool{value: value}
}) do
value
end
def to_native(%SCVal{
type: %StellarBase.XDR.SCValType{identifier: :SCV_U128},
value: %StellarBase.XDR.UInt128Parts{
hi: %StellarBase.XDR.UInt64{datum: hi_value},
lo: %StellarBase.XDR.UInt64{datum: lo_value}
}
}) do
(hi_value <<< 64) + lo_value
end
def to_native(%SCVal{
type: %StellarBase.XDR.SCValType{identifier: :SCV_U64},
value: %StellarBase.XDR.UInt64{datum: value}
}) do
value
end
def to_native(%SCVal{
type: %StellarBase.XDR.SCValType{identifier: :SCV_I64},
value: %StellarBase.XDR.Int64{datum: value}
}) do
value
end
def to_native(%SCVal{
type: %StellarBase.XDR.SCValType{identifier: :SCV_U32},
value: %StellarBase.XDR.UInt32{datum: value}
}) do
value
end
def to_native(%SCVal{
type: %StellarBase.XDR.SCValType{identifier: :SCV_I32},
value: %StellarBase.XDR.Int32{datum: value}
}) do
value
end
def to_native(%SCVal{
type: %StellarBase.XDR.SCValType{identifier: :SCV_I128},
value: %StellarBase.XDR.Int128Parts{
hi: %StellarBase.XDR.Int64{datum: hi_value},
lo: %StellarBase.XDR.UInt64{datum: lo_value}
}
}) do
(hi_value <<< 64) + lo_value
end
def to_native(%SCVal{
type: %StellarBase.XDR.SCValType{identifier: :SCV_U256},
value: %StellarBase.XDR.UInt256Parts{
hi_hi: %StellarBase.XDR.UInt64{datum: hi_hi_value},
hi_lo: %StellarBase.XDR.UInt64{datum: hi_lo_value},
lo_hi: %StellarBase.XDR.UInt64{datum: lo_hi_value},
lo_lo: %StellarBase.XDR.UInt64{datum: lo_lo_value}
}
}) do
(hi_hi_value <<< 192) + (hi_lo_value <<< 128) + (lo_hi_value <<< 64) + lo_lo_value
end
def to_native(%SCVal{
type: %StellarBase.XDR.SCValType{identifier: :SCV_I256},
value: %StellarBase.XDR.Int256Parts{
hi_hi: %StellarBase.XDR.Int64{datum: hi_hi_value},
hi_lo: %StellarBase.XDR.UInt64{datum: hi_lo_value},
lo_hi: %StellarBase.XDR.UInt64{datum: lo_hi_value},
lo_lo: %StellarBase.XDR.UInt64{datum: lo_lo_value}
}
}) do
(hi_hi_value <<< 192) + (hi_lo_value <<< 128) + (lo_hi_value <<< 64) + lo_lo_value
end
def to_native(%SCVal{
type: %StellarBase.XDR.SCValType{identifier: :SCV_BYTES},
value: %StellarBase.XDR.SCBytes{value: value}
}) do
value
end
def to_native(%SCVal{
type: %StellarBase.XDR.SCValType{identifier: :SCV_STRING},
value: %StellarBase.XDR.SCString{value: value}
}) do
value
end
def to_native(%SCVal{
type: %StellarBase.XDR.SCValType{identifier: :SCV_MAP},
value: %StellarBase.XDR.OptionalSCMap{
sc_map: %StellarBase.XDR.SCMap{
items: items
}
}
}) do
Enum.reduce(items, %{}, fn entry, acc ->
{key, val} = map_entry_to_native(entry)
Map.put(acc, key, val)
end)
end
def to_native(%SCVal{
type: %StellarBase.XDR.SCValType{identifier: :SCV_MAP},
value: %StellarBase.XDR.OptionalSCMap{
sc_map: nil
}
}) do
%{}
end
def to_native(%SCVal{
type: %StellarBase.XDR.SCValType{identifier: :SCV_ADDRESS},
value: %StellarBase.XDR.SCAddress{
sc_address: %StellarBase.XDR.AccountID{
account_id: %StellarBase.XDR.PublicKey{
public_key: %StellarBase.XDR.UInt256{
datum: public_key
}
}
},
type: %StellarBase.XDR.SCAddressType{identifier: :SC_ADDRESS_TYPE_ACCOUNT}
}
}) do
StellarBase.StrKey.encode!(public_key, :ed25519_public_key)
end
def to_native(%SCVal{
type: %StellarBase.XDR.SCValType{identifier: :SCV_ADDRESS},
value: %StellarBase.XDR.SCAddress{
sc_address: %StellarBase.XDR.Hash{value: hash},
type: %StellarBase.XDR.SCAddressType{identifier: :SC_ADDRESS_TYPE_CONTRACT}
}
}) do
StellarBase.StrKey.encode!(hash, :contract)
end
def to_native(_sc_val), do: {:error, :invalid_or_not_supported_sc_val}
@spec validate_xdr_decoding(xdr :: String.t()) :: validation()
defp validate_xdr_decoding(xdr) when is_binary(xdr) do
case Base.decode64(xdr) do
{:ok, decoded_xdr} -> SCVal.decode_xdr(decoded_xdr)
_ -> {:error, :invalid_base64}
end
end
@spec map_entry_to_native(map_entry :: map_entry()) :: tuple()
defp map_entry_to_native(%StellarBase.XDR.SCMapEntry{key: key, val: val}),
do: {to_native(key), to_native(val)}
@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({:error, %SCError{} = 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, %{hi_hi: hi_hi, hi_lo: hi_lo, lo_hi: lo_hi, lo_lo: lo_lo} = value})
when type in ~w(u256 i256)a and is_integer(hi_hi) and is_integer(hi_lo) and
is_integer(lo_hi) and is_integer(lo_lo),
do: {:ok, value}
defp validate_sc_val({type, value})
when type in ~w(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_instance, :token}), do: {:ok, :token}
defp validate_sc_val({:contract_instance, {: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_instance, _value}), do: {:ok, nil}
defp validate_sc_val({:ledger_key_nonce, value}) when is_integer(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