defmodule Ethers.EventFilter do
@moduledoc """
Event Filter struct and helper functions to work with the event filters
"""
@typedoc """
Holds event filter topics, the event selector and the default address.
Can be passed in to `Ethers.get_logs/2` filter and fetch the logs.
"""
@type t :: %__MODULE__{
topics: [binary()],
selector: ABI.FunctionSelector.t(),
default_address: nil | Ethers.Types.t_address()
}
@enforce_keys [:topics, :selector]
defstruct [:topics, :selector, :default_address]
@doc false
@spec new([binary()], ABI.FunctionSelector.t(), Ethers.Types.t_address() | nil) :: t()
def new(topics, selector, default_address) do
%__MODULE__{
topics: topics,
selector: selector,
default_address: default_address
}
end
@doc false
@spec to_map(t() | map(), Keyword.t()) :: map()
def to_map(%__MODULE__{} = tx_data, overrides) do
tx_data
|> event_filter_map()
|> to_map(overrides)
end
def to_map(event_filter, overrides) do
Enum.into(overrides, event_filter)
end
defp event_filter_map(%{selector: %{type: :event}} = event_filter) do
%{topics: event_filter.topics}
|> maybe_add_address(event_filter.default_address)
end
defp maybe_add_address(tx_map, nil), do: tx_map
defp maybe_add_address(tx_map, address), do: Map.put(tx_map, :address, address)
defimpl Inspect do
import Inspect.Algebra
alias Ethers.Utils
def inspect(
%{selector: selector, topics: [_t0 | topics], default_address: default_address},
opts
) do
default_address =
case default_address do
nil ->
[]
_ ->
[
line(),
color("default_address: ", :default, opts),
color(inspect(default_address), :string, opts)
]
end
argument_doc =
case argument_doc(selector, topics, opts) do
[] ->
[
color("(", :operator, opts),
color(")", :operator, opts)
]
argument_doc ->
[
color("(", :operator, opts),
nest(concat([break("") | argument_doc]), 2),
break(""),
color(")", :operator, opts)
]
end
inner =
concat(
[
break(""),
color("event", :atom, opts),
" ",
color(selector.function, :call, opts)
] ++ argument_doc ++ default_address
)
concat([
color("#Ethers.EventFilter<", :map, opts),
nest(inner, 2),
break(""),
color(">", :map, opts)
])
end
defp input_names(selector) do
if Enum.count(selector.types) == Enum.count(selector.input_names) do
selector.input_names
else
1..Enum.count(selector.types)
|> Enum.map(fn _ -> nil end)
end
end
defp argument_doc(selector, topics, opts),
do:
argument_doc(
selector.types,
input_names(selector),
selector.inputs_indexed,
topics,
[],
opts
)
defp argument_doc(types, input_names, inputs_indexed, topics, acc, opts)
defp argument_doc([], _, _, _, acc, opts) do
Enum.reverse(acc)
|> Enum.intersperse(concat(color(",", :operator, opts), break(" ")))
end
defp argument_doc(
[type | types],
[name | input_names],
[true | inputs_indexed],
[topic | topics],
acc,
opts
) do
doc =
[
color(ABI.FunctionSelector.encode_type(type), :atom, opts),
" ",
color("indexed", nil, opts),
if(name, do: " "),
if(name, do: color(name, :variable, opts)),
" ",
if(is_nil(topic), do: color("any", :string, opts), else: human_topic(type, topic))
]
|> Enum.reject(&is_nil/1)
|> concat()
argument_doc(types, input_names, inputs_indexed, topics, [doc | acc], opts)
end
defp argument_doc(
[type | types],
[name | input_names],
[false | inputs_indexed],
topics,
acc,
opts
) do
doc =
[
color(ABI.FunctionSelector.encode_type(type), :atom, opts),
if(name, do: " "),
if(name, do: color(name, :variable, opts))
]
|> Enum.reject(&is_nil/1)
|> concat()
argument_doc(types, input_names, inputs_indexed, topics, [doc | acc], opts)
end
defp human_topic(type, topic) do
hashed? =
case type do
type when type in [:string, :bytes] -> true
{:array, _} -> true
{:array, _, _} -> true
{:tuple, _} -> true
_ -> false
end
if hashed? do
"(hashed) #{inspect(topic)}"
else
[value] =
Utils.hex_decode!(topic)
|> ABI.TypeDecoder.decode([type])
inspect(Utils.human_arg(value, type))
end
end
end
end