Current section

Files

Jump to
ex_abi lib abi function_selector.ex
Raw

lib/abi/function_selector.ex

defmodule ABI.FunctionSelector do
@moduledoc """
Module to help parse the ABI function signatures, e.g.
`my_function(uint64, string[])`.
"""
require Integer
@type type ::
{:uint, integer()}
| :bool
| :bytes
| :string
| :address
| {:array, type}
| {:array, type, non_neg_integer}
| {:tuple, [type]}
@type t :: %__MODULE__{
function: String.t(),
types: [type],
returns: type
}
defstruct [:function, :types, :returns]
@doc """
Decodes a function selector to a struct.
## Examples
iex> ABI.FunctionSelector.decode("bark(uint256,bool)")
%ABI.FunctionSelector{
function: "bark",
types: [
{:uint, 256},
:bool
]
}
iex> ABI.FunctionSelector.decode("growl(uint,address,string[])")
%ABI.FunctionSelector{
function: "growl",
types: [
{:uint, 256},
:address,
{:array, :string}
]
}
iex> ABI.FunctionSelector.decode("rollover()")
%ABI.FunctionSelector{
function: "rollover",
types: []
}
iex> ABI.FunctionSelector.decode("do_playDead3()")
%ABI.FunctionSelector{
function: "do_playDead3",
types: []
}
iex> ABI.FunctionSelector.decode("pet(address[])")
%ABI.FunctionSelector{
function: "pet",
types: [
{:array, :address}
]
}
iex> ABI.FunctionSelector.decode("paw(string[2])")
%ABI.FunctionSelector{
function: "paw",
types: [
{:array, :string, 2}
]
}
iex> ABI.FunctionSelector.decode("scram(uint256[])")
%ABI.FunctionSelector{
function: "scram",
types: [
{:array, {:uint, 256}}
]
}
iex> ABI.FunctionSelector.decode("shake((string))")
%ABI.FunctionSelector{
function: "shake",
types: [
{:tuple, [:string]}
]
}
"""
def decode(signature) do
ABI.Parser.parse!(signature, as: :selector)
end
@doc """
Decodes the given type-string as a simple array of types.
## Examples
iex> ABI.FunctionSelector.decode_raw("string,uint256")
[:string, {:uint, 256}]
iex> ABI.FunctionSelector.decode_raw("")
[]
"""
def decode_raw(type_string) do
{:tuple, types} = decode_type("(#{type_string})")
types
end
@doc false
def parse_specification_item(%{"type" => "function"} = item) do
%{
"name" => function_name,
"inputs" => named_inputs,
"outputs" => named_outputs
} = item
input_types = Enum.map(named_inputs, &parse_specification_type/1)
output_types = Enum.map(named_outputs, &parse_specification_type/1)
%ABI.FunctionSelector{
function: function_name,
types: input_types,
returns: List.first(output_types)
}
end
def parse_specification_item(%{"type" => "fallback"}) do
%ABI.FunctionSelector{
function: nil,
types: [],
returns: nil
}
end
def parse_specification_item(_), do: nil
defp parse_specification_type(%{"type" => type}), do: decode_type(type)
@doc """
Decodes the given type-string as a single type.
## Examples
iex> ABI.FunctionSelector.decode_type("uint256")
{:uint, 256}
iex> ABI.FunctionSelector.decode_type("(bool,address)")
{:tuple, [:bool, :address]}
iex> ABI.FunctionSelector.decode_type("address[][3]")
{:array, {:array, :address}, 3}
"""
def decode_type(single_type) do
ABI.Parser.parse!(single_type, as: :type)
end
@doc """
Encodes a function call signature.
## Examples
iex> ABI.FunctionSelector.encode(%ABI.FunctionSelector{
...> function: "bark",
...> types: [
...> {:uint, 256},
...> :bool,
...> {:array, :string},
...> {:array, :string, 3},
...> {:tuple, [{:uint, 256}, :bool]}
...> ]
...> })
"bark(uint256,bool,string[],string[3],(uint256,bool))"
"""
def encode(function_selector) do
types = get_types(function_selector) |> Enum.join(",")
"#{function_selector.function}(#{types})"
end
defp get_types(function_selector) do
for type <- function_selector.types do
get_type(type)
end
end
defp get_type(nil), do: nil
defp get_type({:int, size}), do: "int#{size}"
defp get_type({:uint, size}), do: "uint#{size}"
defp get_type(:address), do: "address"
defp get_type(:bool), do: "bool"
defp get_type({:fixed, element_count, precision}), do: "fixed#{element_count}x#{precision}"
defp get_type({:ufixed, element_count, precision}), do: "ufixed#{element_count}x#{precision}"
defp get_type({:bytes, size}), do: "bytes#{size}"
defp get_type(:function), do: "function"
defp get_type({:array, type, element_count}), do: "#{get_type(type)}[#{element_count}]"
defp get_type(:bytes), do: "bytes"
defp get_type(:string), do: "string"
defp get_type({:array, type}), do: "#{get_type(type)}[]"
defp get_type({:tuple, types}) do
encoded_types = Enum.map(types, &get_type/1)
"(#{Enum.join(encoded_types, ",")})"
end
defp get_type(els), do: raise("Unsupported type: #{inspect(els)}")
@doc false
@spec is_dynamic?(ABI.FunctionSelector.type()) :: boolean
def is_dynamic?(:bytes), do: true
def is_dynamic?(:string), do: true
def is_dynamic?({:array, _type}), do: true
def is_dynamic?({:array, type, len}) when len > 0, do: is_dynamic?(type)
def is_dynamic?({:tuple, types}), do: Enum.any?(types, &is_dynamic?/1)
def is_dynamic?(_), do: false
end