ex_abi/lib/abi/function_selector.ex

476 lines
12 KiB
Elixir

defmodule ABI.FunctionSelector do
@moduledoc """
Module to help parse the ABI function signatures, e.g.
`my_function(uint64, string[])`.
"""
require Integer
require Logger
@type type ::
{:uint, integer()}
| :bool
| :string
| :address
| :function
| {:array, type}
| {:array, type, non_neg_integer}
| {:tuple, [type]}
| :bytes
| {:bytes, non_neg_integer}
| {:ufixed, non_neg_integer, non_neg_integer}
| {:fixed, non_neg_integer, non_neg_integer}
| {:int, integer}
@typedoc """
Struct to represent a function and its input and output types.
* `:function` - Name of the function
* `:types` - Function's input types
* `:returns` - Function's return types
* `:return_names` - Names of the return values (output names)
* `:method_id` - First four bytes of the hashed function signature or full 32 byte hash for events
* `:input_names` - Names of the input values (argument names)
* `:type` - The type of the selector. Events are part of the ABI, but are not considered functions
* `:inputs_index` - A list of true/false values denoting if each input is indexed. Only populated for events.
"""
@type t :: %__MODULE__{
function: String.t() | nil,
method_id: binary | nil,
input_names: [String.t()],
types: [type],
returns: [type],
return_names: [String.t()],
type: :event | :function | :constructor | :error,
state_mutability: :pure | :view | :non_payable | :payable | nil,
inputs_indexed: [boolean]
}
defstruct [
:function,
:method_id,
:type,
:inputs_indexed,
:state_mutability,
input_names: [],
types: [],
returns: [],
return_names: []
]
@simple_types [
"uint",
"int",
"address",
"bool",
"fixed",
"uint",
"ufixed",
"bytes",
"string",
"tuple"
]
@state_mutabilities %{
"pure" => :pure,
"view" => :view,
"nonpayable" => :non_payable,
"payable" => :payable
}
@doc """
Decodes a function selector to a struct.
## Examples
iex> ABI.FunctionSelector.decode("bark(uint256,bool)")
%ABI.FunctionSelector{
function: "bark",
types: [
{:uint, 256},
:bool
],
returns: []
}
iex> ABI.FunctionSelector.decode("growl(uint,address,string[])")
%ABI.FunctionSelector{
function: "growl",
types: [
{:uint, 256},
:address,
{:array, :string}
],
returns: []
}
iex> ABI.FunctionSelector.decode("rollover()")
%ABI.FunctionSelector{
function: "rollover",
types: [],
returns: []
}
iex> ABI.FunctionSelector.decode("do_playDead3()")
%ABI.FunctionSelector{
function: "do_playDead3",
types: [],
returns: []
}
iex> ABI.FunctionSelector.decode("pet(address[])")
%ABI.FunctionSelector{
function: "pet",
types: [
{:array, :address}
],
returns: []
}
iex> ABI.FunctionSelector.decode("paw(string[2])")
%ABI.FunctionSelector{
function: "paw",
types: [
{:array, :string, 2}
],
returns: []
}
iex> ABI.FunctionSelector.decode("scram(uint256[])")
%ABI.FunctionSelector{
function: "scram",
types: [
{:array, {:uint, 256}}
],
returns: []
}
iex> ABI.FunctionSelector.decode("shake((string))")
%ABI.FunctionSelector{
function: "shake",
types: [
{:tuple, [:string]}
],
returns: []
}
"""
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
with %{
"name" => function_name,
"inputs" => named_inputs,
"outputs" => named_outputs
} <-
item
# Workaround for ABIs that are missing the "outputs" field
|> Map.put_new("outputs", []),
true <- simple_types?(named_inputs, item),
true <- simple_types?(named_outputs, item) do
input_types = Enum.map(named_inputs, &parse_specification_type/1)
input_names = Enum.map(named_inputs, &Map.get(&1, "name"))
output_types = Enum.map(named_outputs, &parse_specification_type/1)
output_names = Enum.map(named_outputs, &Map.get(&1, "name"))
selector = %ABI.FunctionSelector{
function: function_name,
types: input_types,
returns: output_types,
return_names: output_names,
input_names: input_names,
state_mutability: @state_mutabilities[item["stateMutability"]],
type: :function
}
add_method_id(selector)
else
_ -> nil
end
end
def parse_specification_item(%{"type" => "constructor"} = item) do
with %{"inputs" => named_inputs} <- item,
true <- simple_types?(named_inputs, item) do
input_types = Enum.map(named_inputs, &parse_specification_type/1)
input_names = Enum.map(named_inputs, &Map.get(&1, "name"))
selector = %ABI.FunctionSelector{
types: input_types,
input_names: input_names,
type: :constructor
}
add_method_id(selector)
else
_ -> nil
end
end
def parse_specification_item(%{"type" => "error"} = item) do
with %{"inputs" => named_inputs, "name" => name} <- item,
true <- simple_types?(named_inputs, item) do
input_types = Enum.map(named_inputs, &parse_specification_type/1)
input_names = Enum.map(named_inputs, &Map.get(&1, "name"))
selector = %ABI.FunctionSelector{
function: name,
types: input_types,
input_names: input_names,
type: :error
}
add_method_id(selector)
else
_ -> nil
end
end
def parse_specification_item(%{"type" => "event"} = item) do
with %{
"name" => event_name,
"inputs" => named_inputs
} <- item,
true <- simple_types?(named_inputs, item) do
input_types = Enum.map(named_inputs, &parse_specification_type/1)
input_names = Enum.map(named_inputs, &Map.get(&1, "name"))
inputs_indexed = Enum.map(named_inputs, &Map.get(&1, "indexed"))
selector = %ABI.FunctionSelector{
function: event_name,
types: input_types,
input_names: input_names,
inputs_indexed: inputs_indexed,
type: :event
}
add_event_id(selector)
else
_ -> nil
end
end
def parse_specification_item(%{"type" => "fallback"}) do
%ABI.FunctionSelector{
function: nil,
method_id: nil,
input_names: [],
types: [],
returns: [],
return_names: [],
type: :function
}
end
def parse_specification_item(_), do: nil
@spec simple_types?([map()], map()) :: boolean()
def simple_types?([], _item), do: true
def simple_types?([%{"type" => tuple_type, "components" => current_types} | types], item)
when tuple_type in ["tuple", "tuple[]", "tuple[][]"] do
case simple_types?(current_types, item) do
true ->
simple_types?(types, item)
false ->
Logger.warning("Can not parse #{inspect(item)} because it contains complex types")
false
end
end
def simple_types?([%{"type" => type} | types], item) do
simple_type? =
Enum.any?(@simple_types, fn simple_type ->
String.contains?(type, simple_type)
end)
case simple_type? do
true ->
simple_types?(types, item)
false ->
Logger.warning("Can not parse #{inspect(item)} because it contains complex types")
false
end
end
defp replace_tuple({:array, inner}, sub_types) do
{:array, replace_tuple(inner, sub_types)}
end
defp replace_tuple({:array, inner, size}, sub_types) do
{:array, replace_tuple(inner, sub_types), size}
end
defp replace_tuple(:tuple, sub_types) do
{:tuple, sub_types}
end
defp replace_tuple(other, _) do
other
end
def parse_specification_type(%{"type" => "tuple" <> _ = type, "components" => components}) do
sub_types = for component <- components, do: parse_specification_type(component)
type
|> decode_type()
|> replace_tuple(sub_types)
end
def 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 the given single type as a type-string.
## Examples
iex> ABI.FunctionSelector.encode_type({:uint, 256})
"uint256"
iex> ABI.FunctionSelector.encode_type({:tuple, [:bool, :address]})
"(bool,address)"
iex> ABI.FunctionSelector.encode_type({:array, {:array, :address}, 3})
"address[][3]"
"""
def encode_type(single_type) do
get_type(single_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 add_method_id(selector) do
signature = encode(selector)
case ExKeccak.hash_256(signature) do
<<method_id::binary-size(4), _::binary>> ->
%{selector | method_id: method_id}
_ ->
selector
end
end
defp add_event_id(selector) do
signature = encode(selector)
%{selector | method_id: ExKeccak.hash_256(signature)}
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 dynamic?(ABI.FunctionSelector.type()) :: boolean
def dynamic?(:bytes), do: true
def dynamic?(:string), do: true
def dynamic?({:array, _type}), do: true
def dynamic?({:array, type, len}) when len > 0, do: dynamic?(type)
def dynamic?({:tuple, types}), do: Enum.any?(types, &dynamic?/1)
def dynamic?(_), do: false
@doc false
def from_params(params) when is_map(params) do
formatted_params =
params
|> Map.take(~w(function types returns)a)
|> Enum.map(&sanitize_param/1)
struct!(ABI.FunctionSelector, formatted_params)
end
defp sanitize_param({key, nil}) when key in ~w(types returns)a do
{key, []}
end
defp sanitize_param(tuple), do: tuple
end