Packages
abi
1.0.0-alpha7
1.3.0
1.2.0
1.1.0
1.0.0-bravo6
1.0.0-bravo4
1.0.0-bravo3
1.0.0-bravo2
1.0.0-bravo1
1.0.0-alpha9
1.0.0-alpha8
1.0.0-alpha7
1.0.0-alpha6
1.0.0-alpha5
1.0.0-alpha4
1.0.0-alpha3
1.0.0-alpha2
1.0.0-alpha1
0.1.21
0.1.21-alpha2
0.1.21-alpha1
0.1.20
0.1.19
0.1.18
0.1.17
0.1.16
0.1.15
0.1.13
0.1.12
0.1.11
0.1.10
0.1.9
0.1.8
0.1.7
0.1.6
0.1.5
0.1.4
0.1.3
0.1.2
0.1.1
0.1.0
Ethereum's ABI Interface
Current section
Files
Jump to
Current section
Files
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, [argument_type]}
@type argument_type ::
%{:type => type, optional(:name) => String.t(), optional(:indexed) => boolean()}
@type function_type :: :function | :constructor | :fallback | :receive | :error | :event
@type state_mutability :: :nonpayable | :pure | :view | :payable
@type t :: %__MODULE__{
function: String.t(),
function_type: function_type() | nil,
state_mutability: state_mutability() | nil,
types: [argument_type],
returns: type
}
defstruct [:function, :function_type, :state_mutability, :types, :returns]
@doc """
Decodes a function selector to a struct.
## Examples
iex> ABI.FunctionSelector.decode("bark(uint256,bool)")
%ABI.FunctionSelector{
function: "bark",
types: [
%{type: {:uint, 256}},
%{type: :bool}
]
}
iex> ABI.FunctionSelector.decode("bark(uint256 name, bool loud)")
%ABI.FunctionSelector{
function: "bark",
types: [
%{type: {:uint, 256}, name: "name"},
%{type: :bool, name: "loud"}
]
}
iex> ABI.FunctionSelector.decode("bark(uint256 name,bool indexed loud)")
%ABI.FunctionSelector{
function: "bark",
types: [
%{type: {:uint, 256}, name: "name"},
%{type: :bool, name: "loud", indexed: true}
]
}
iex> ABI.FunctionSelector.decode("(uint256,bool)")
%ABI.FunctionSelector{
function: nil,
types: [
%{type: {:uint, 256}},
%{type: :bool}
]
}
iex> ABI.FunctionSelector.decode("growl(uint,address,string[])")
%ABI.FunctionSelector{
function: "growl",
types: [
%{type: {:uint, 256}},
%{type: :address},
%{type: {: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: [
%{type: {:array, :address}}
]
}
iex> ABI.FunctionSelector.decode("paw(string[2])")
%ABI.FunctionSelector{
function: "paw",
types: [
%{type: {:array, :string, 2}}
]
}
iex> ABI.FunctionSelector.decode("scram(uint256[])")
%ABI.FunctionSelector{
function: "scram",
types: [
%{type: {:array, {:uint, 256}}}
]
}
iex> ABI.FunctionSelector.decode("shake((string))")
%ABI.FunctionSelector{
function: "shake",
types: [
%{type: {:tuple, [%{type: :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})")
Enum.map(types, fn argument_type -> argument_type.type end)
end
@doc """
Parse a function selector, e.g. from an abi.json file.
## Examples
iex> ABI.FunctionSelector.parse_specification_item(%{"type" => "function", "name" => "fun", "inputs" => [%{"name" => "a", "type" => "uint96", "internalType" => "uint96"}]})
%ABI.FunctionSelector{
function: "fun",
function_type: :function,
types: [%{type: {:uint, 96}, name: "a"}],
returns: nil
}
iex> ABI.FunctionSelector.parse_specification_item(%{"type" => "function", "name" => "fun", "inputs" => [%{"name" => "s", "type" => "tuple", "internalType" => "tuple", "components" => [%{"name" => "a", "type" => "uint256", "internalType" => "uint256"},%{"name" => "b", "type" => "address", "internalType" => "address"},%{"name" => "c", "type" => "bytes", "internalType" => "bytes"}]},%{"name" => "d", "type" => "uint256", "internalType" => "uint256"}],"outputs" => [%{"name" => "", "type" => "bytes", "internalType" => "bytes"}],"stateMutability" => "view"})
%ABI.FunctionSelector{
function: "fun",
function_type: :function,
state_mutability: :view,
types: [
%{
type:
{:tuple, [
%{type: {:uint, 256}, name: "a"},
%{type: :address, name: "b"},
%{type: :bytes, name: "c"}
]},
name: "s"
}, %{
type: {:uint, 256},
name: "d"
}
],
returns: [%{name: "", type: :bytes}],
}
iex> ABI.FunctionSelector.parse_specification_item(%{"type" => "function", "name" => "fun", "inputs" => [%{"name" => "s", "type" => "tuple", "internalType" => "struct Contract.Struct", "components" => [%{"name" => "a", "type" => "uint256", "internalType" => "uint256"},%{"name" => "b", "type" => "address", "internalType" => "address"},%{"name" => "c", "type" => "bytes", "internalType" => "bytes"}]},%{"name" => "d", "type" => "uint256", "internalType" => "uint256"}],"outputs" => [%{"name" => "", "type" => "bytes", "internalType" => "bytes"}],"stateMutability" => "pure"})
%ABI.FunctionSelector{
function: "fun",
function_type: :function,
state_mutability: :pure,
types: [
%{
type:
{:tuple, [
%{type: {:uint, 256}, name: "a"},
%{type: :address, name: "b"},
%{type: :bytes, name: "c"}
]},
name: "s"
}, %{
type: {:uint, 256},
name: "d"
}
],
returns: [%{name: "", type: :bytes}],
}
iex> ABI.FunctionSelector.parse_specification_item(%{"type" => "function", "name" => "fun", "inputs" => [%{"name" => "s", "type" => "tuple", "internalType" => "struct Contract.Struct", "components" => [%{"name" => "a", "type" => "uint256", "internalType" => "uint256"},%{"type" => "address", "internalType" => "address"},%{"name" => "c", "type" => "bytes", "internalType" => "bytes"}]},%{"name" => "d", "type" => "uint256", "internalType" => "uint256"}],"outputs" => [%{"name" => "", "type" => "bytes", "internalType" => "bytes"}],"stateMutability" => "payable"})
%ABI.FunctionSelector{
function: "fun",
function_type: :function,
state_mutability: :payable,
types: [
%{
type:
{:tuple, [
%{type: {:uint, 256}, name: "a"},
%{type: :address},
%{type: :bytes, name: "c"}
]},
name: "s"
}, %{
type: {:uint, 256},
name: "d"
}
],
returns: [%{name: "", type: :bytes}],
}
iex> ABI.FunctionSelector.parse_specification_item(%{"type" => "fallback"})
%ABI.FunctionSelector{
function: nil,
function_type: :fallback,
types: [],
returns: nil
}
iex> ABI.FunctionSelector.parse_specification_item(%{"type" => "receive"})
%ABI.FunctionSelector{
function: nil,
function_type: :receive,
types: [],
returns: nil
}
iex> ABI.FunctionSelector.parse_specification_item(%{"inputs" => [%{"internalType" => "address[]", "name" => "xs", "type" => "address[]"}, %{"internalType" => "bytes[]", "name" => "ys", "type" => "bytes[]"}, %{"components" => [%{"internalType" => "enum Z", "name" => "za", "type" => "uint8"}, %{"internalType" => "enum Z", "name" => "zb", "type" => "uint8"}], "internalType" => "struct Z.Z[]", "name" => "zs", "type" => "tuple[]"}, %{"internalType" => "bytes[]", "name" => "zz", "type" => "bytes[]"}], "name" => "go", "outputs" => [%{"internalType" => "bytes[]", "name" => "", "type" => "bytes[]"}], "stateMutability" => "nonpayable", "type" => "function"})
%ABI.FunctionSelector{
function: "go",
function_type: :function,
state_mutability: :nonpayable,
types: [
%{name: "xs", type: {:array, :address}},
%{name: "ys", type: {:array, :bytes}},
%{
name: "zs",
type:
{:array,
{:tuple,
[%{name: "za", type: {:uint, 8}}, %{name: "zb", type: {:uint, 8}}]}}
},
%{name: "zz", type: {:array, :bytes}}
],
returns: [%{name: "", type: {:array, :bytes}}]
}
iex> ABI.FunctionSelector.parse_specification_item(%{"anonymous" => false, "inputs" => [%{"indexed" => true, "internalType" => "address", "name" => "z0", "type" => "address"}, %{"indexed" => true, "internalType" => "address", "name" => "z1", "type" => "address"}, %{"indexed" => false, "internalType" => "address", "name" => "z2", "type" => "address"}, %{"indexed" => false, "internalType" => "bytes32", "name" => "z3", "type" => "bytes32"}], "name" => "z4", "type" => "event"})
%ABI.FunctionSelector{
function: "z4",
function_type: :event,
state_mutability: nil,
types: [
%{name: "z0", type: :address, indexed: true},
%{name: "z1", type: :address, indexed: true},
%{name: "z2", type: :address, indexed: false},
%{name: "z3", type: {:bytes, 32}, indexed: false}
],
returns: nil
}
iex> ABI.FunctionSelector.parse_specification_item(%{"inputs" => [], "name" => "Abc", "type" => "error"})
%ABI.FunctionSelector{function: "Abc", function_type: :error, state_mutability: nil, types: [], returns: nil}
"""
def parse_specification_item(%{"type" => function_type} = item) do
input_types = Enum.map(Map.get(item, "inputs", []), &parse_specification_type/1)
output_types =
if Map.has_key?(item, "outputs"),
do: Enum.map(item["outputs"], &parse_specification_type/1),
else: nil
state_mutability =
if Map.has_key?(item, "stateMutability"),
do: get_state_mutability(item["stateMutability"]),
else: nil
%ABI.FunctionSelector{
function: Map.get(item, "name", nil),
function_type: get_function_type(function_type),
state_mutability: state_mutability,
types: input_types,
returns: output_types
}
end
defp parse_specification_type(record=%{"name" => name, "indexed" => indexed}) do
%{name: name, type: parse_specification_type_type(record), indexed: indexed}
end
defp parse_specification_type(record=%{"indexed" => indexed}) do
%{type: parse_specification_type_type(record), indexed: indexed}
end
defp parse_specification_type(record=%{"name" => name}) do
%{name: name, type: parse_specification_type_type(record)}
end
defp parse_specification_type(record) do
%{type: parse_specification_type_type(record)}
end
defp parse_specification_type_type(%{"type" => "tuple[]", "components" => components}) do
{:array, {:tuple, Enum.map(components, &parse_specification_type/1)}}
end
defp parse_specification_type_type(%{"type" => "tuple", "components" => components}) do
{:tuple, Enum.map(components, &parse_specification_type/1)}
end
defp parse_specification_type_type(%{"type" => type}) do
decode_type(type)
end
@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, [%{type: :bool}, %{type: :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.
## Example
iex> ABI.FunctionSelector.encode(%ABI.FunctionSelector{
...> function: "bark",
...> types: [
...> %{type: {:uint, 256}},
...> %{type: :bool},
...> %{type: {:array, :string}},
...> %{type: {:array, :string, 3}},
...> %{type: {:tuple, [%{type: {:uint, 256}}, %{type: :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: 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, fn argument_type -> get_type(argument_type.type) end)
"(#{Enum.join(encoded_types, ",")})"
end
defp get_type({:struct, _name, types, _names}) 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, fn arg_type -> is_dynamic?(arg_type.type) end)
def is_dynamic?({:bytes,_}), do: false
def is_dynamic?({:int,_}), do: false
def is_dynamic?({:uint,_}), do: false
def is_dynamic?(:bool), do: false
def is_dynamic?(:address), do: false
@doc false
@spec get_function_type(String.t()) :: function_type()
def get_function_type("function"), do: :function
def get_function_type("constructor"), do: :constructor
def get_function_type("receive"), do: :receive
def get_function_type("fallback"), do: :fallback
def get_function_type("error"), do: :error
def get_function_type("event"), do: :event
@doc false
@spec get_state_mutability(String.t()) :: state_mutability()
def get_state_mutability("nonpayable"), do: :nonpayable
def get_state_mutability("pure"), do: :pure
def get_state_mutability("view"), do: :view
def get_state_mutability("payable"), do: :payable
end