Skip to content

Commit

Permalink
start to build event signatures
Browse files Browse the repository at this point in the history
Add support for events

Fix decoding issue for ABI, push to 0.1.20
  • Loading branch information
hayesgm committed Feb 8, 2024
1 parent dfe5cfa commit 409ad9d
Show file tree
Hide file tree
Showing 10 changed files with 351 additions and 119 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ by adding `abi` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:abi, "~> 0.1.21"}
{:abi, "~> 1.0.0-alpha1"}
]
end
```
Expand Down
69 changes: 60 additions & 9 deletions lib/abi.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ defmodule ABI do
...> |> Base.encode16(case: :lower)
"000000000000000000000000000000000000000000000000000000000000000a"
iex> ABI.encode("baz(uint,address)", [50, <<1::160>> |> :binary.decode_unsigned])
iex> ABI.encode("baz(uint,address)", [50, <<1::160>>])
...> |> Base.encode16(case: :lower)
"a291add600000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000001"
Expand All @@ -27,7 +27,7 @@ defmodule ABI do
iex> ABI.encode("baz(uint8)", [9999])
** (RuntimeError) Data overflow encoding uint, data `9999` cannot fit in 8 bits
iex> ABI.encode("(uint,address)", [{50, <<1::160>> |> :binary.decode_unsigned}])
iex> ABI.encode("(uint,address)", [{50, <<1::160>>}])
...> |> Base.encode16(case: :lower)
"00000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000001"
Expand All @@ -43,7 +43,7 @@ defmodule ABI do
...> |> Jason.decode!
...> |> ABI.parse_specification
...> |> Enum.find(&(&1.function == "bark")) # bark(address,bool)
...> |> ABI.encode([<<1::160>> |> :binary.decode_unsigned, true])
...> |> ABI.encode([<<1::160>>, true])
...> |> Base.encode16(case: :lower)
"b85d0bd200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001"
"""
Expand Down Expand Up @@ -87,10 +87,56 @@ defmodule ABI do
end

def decode(%ABI.FunctionSelector{} = function_selector, data) do
[res] = ABI.TypeDecoder.decode_raw(data, [{:tuple, function_selector.types}])
[res] = ABI.TypeDecoder.decode_raw(data, [%{type: {:tuple, function_selector.types}}])
Tuple.to_list(res)
end

@doc """
Decodes an event, including indexed and non-indexed data.
## Examples
iex> ABI.decode_event(
...> "Transfer(address indexed from, address indexed to, uint256 amount)",
...> "00000000000000000000000000000000000000000000000000000004a817c800" |> Base.decode16!(case: :lower),
...> [
...> "ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" |> Base.decode16!(case: :lower),
...> "000000000000000000000000b2b7c1795f19fbc28fda77a95e59edbb8b3709c8" |> Base.decode16!(case: :lower),
...> "0000000000000000000000007795126b3ae468f44c901287de98594198ce38ea" |> Base.decode16!(case: :lower)
...> ]
...> )
{"Transfer",
%{
"amount" => 20_000_000_000,
"from" => <<252, 55, 141, 170, 149, 43, 167, 241, 99, 196, 161, 22, 40, 245, 90, 77, 245, 35, 179, 239>>,
"to" => <<178, 183, 193, 121, 95, 25, 251, 194, 143, 218, 119, 169, 94, 89, 237, 187, 139, 55, 9, 200>>
}}
"""
def decode_event(function_signature, data, topics) when is_binary(function_signature) do
decode_event(ABI.FunctionSelector.decode(function_signature), data, topics)
end

def decode_event(%ABI.FunctionSelector{} = function_selector, data, topics) do
ABI.Event.decode_event(data, topics, function_selector)
end

@doc """
Decodes an event, including indexed and non-indexed data.
## Examples
iex> ABI.event_topic("Transfer(address indexed from, address indexed to, uint256 amount)")
...> |> Base.encode16(case: :lower)
"ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
"""
def event_topic(function_signature) when is_binary(function_signature) do
event_topic(ABI.FunctionSelector.decode(function_signature))
end

def event_topic(%ABI.FunctionSelector{} = function_selector) do
ABI.Event.event_topic(function_selector)
end

@doc """
Parses the given ABI specification document into an array of `ABI.FunctionSelector`s.
Expand All @@ -103,8 +149,8 @@ defmodule ABI do
iex> File.read!("priv/dog.abi.json")
...> |> Jason.decode!
...> |> ABI.parse_specification
[%ABI.FunctionSelector{function: "bark", returns: nil, types: [:address, :bool], names: ["at", "loudly"]},
%ABI.FunctionSelector{function: "rollover", returns: :bool, types: [], names: []}]
[%ABI.FunctionSelector{function: "bark", returns: [], types: [%{name: "at", type: :address}, %{name: "loudly", type: :bool}]},
%ABI.FunctionSelector{function: "rollover", returns: [%{name: "is_a_good_boy", type: :bool}], types: []}]
iex> [%{
...> "constant" => true,
Expand All @@ -119,7 +165,12 @@ defmodule ABI do
...> "type" => "function"
...> }]
...> |> ABI.parse_specification
[%ABI.FunctionSelector{function: "bark", returns: nil, types: [:address, :bool], names: ["at", "loudly"]}]
[
%ABI.FunctionSelector{function: "bark", returns: [], types: [
%{type: :address, name: "at"},
%{type: :bool, name: "loudly"}
]}
]
iex> [%{
...> "inputs" => [
Expand All @@ -130,7 +181,7 @@ defmodule ABI do
...> "type" => "constructor"
...> }]
...> |> ABI.parse_specification
[]
[%ABI.FunctionSelector{function: nil, types: [%{name: "_numProposals", type: {:uint, 8}}], returns: nil}]
iex> ABI.decode("(string)", "000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000643132333435363738393031323334353637383930313233343536373839303132333435363738393031323334353637383930313233343536373839303132333435363738393031323334353637383930313233343536373839303132333435363738393000000000000000000000000000000000000000000000000000000000" |> Base.decode16!(case: :lower))
[String.duplicate("1234567890", 10)]
Expand All @@ -141,7 +192,7 @@ defmodule ABI do
...> "type" => "fallback"
...> }]
...> |> ABI.parse_specification
[%ABI.FunctionSelector{function: nil, returns: nil, types: [], names: []}]
[%ABI.FunctionSelector{function: nil, returns: nil, types: []}]
"""
def parse_specification(doc) do
doc
Expand Down
78 changes: 78 additions & 0 deletions lib/abi/event.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
defmodule ABI.Event do
@doc ~S"""
Decodes an event, including handling parsing out data from topics.
## Examples
iex> "00000000000000000000000000000000000000000000000000000004a817c800"
...> |> Base.decode16!(case: :lower)
...> |> ABI.Event.decode_event(
...> [
...> "ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" |> Base.decode16!(case: :lower),
...> "000000000000000000000000b2b7c1795f19fbc28fda77a95e59edbb8b3709c8" |> Base.decode16!(case: :lower),
...> "0000000000000000000000007795126b3ae468f44c901287de98594198ce38ea" |> Base.decode16!(case: :lower)
...> ],
...> %ABI.FunctionSelector{
...> function: "Transfer",
...> types: [
...> %{type: :address, name: "from", indexed: true},
...> %{type: :address, name: "to", indexed: true},
...> %{type: {:uint, 256}, name: "amount"},
...> ]
...> })
{"Transfer",
%{
"amount" => 20_000_000_000,
"from" => <<252, 55, 141, 170, 149, 43, 167, 241, 99, 196, 161, 22, 40, 245, 90, 77, 245, 35, 179, 239>>,
"to" => <<178, 183, 193, 121, 95, 25, 251, 194, 143, 218, 119, 169, 94, 89, 237, 187, 139, 55, 9, 200>>
}}
"""
def decode_event(data, topics, function_selector) do
# First, split the types into indexed and not indexed
{indexed_types, non_indexed_types} =
Enum.split_with(function_selector.types, fn t -> Map.get(t, :indexed) end)

indexed_data =
indexed_types
|> Enum.zip(topics)
|> Enum.map(fn {type, topic} ->
[value] = ABI.TypeDecoder.decode_raw(topic, [type])
{type.name, value}
end)
|> Enum.into(%{})

non_indexed_data =
data
|> ABI.TypeDecoder.decode_raw(non_indexed_types)
|> Enum.zip(non_indexed_types)
|> Enum.map(fn {res, %{name: name}} -> {name, res} end)
|> Enum.into(%{})

{function_selector.function, Map.merge(indexed_data, non_indexed_data)}
end

@doc ~S"""
Returns the topic of an event, i.e. the first item that appears
in an Ethereum log for this event.
## Examples
iex> ABI.Event.event_topic(
...> %ABI.FunctionSelector{
...> function: "Transfer",
...> types: [
...> %{type: :address, name: "from", indexed: true},
...> %{type: :address, name: "to", indexed: true},
...> %{type: {:uint, 256}, name: "amount"},
...> ]
...> }
...> )
...> |> Base.encode16(case: :lower)
"ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
"""
def event_topic(function_selector) do
function_selector
|> ABI.FunctionSelector.encode()
|> ABI.Math.kec()
end
end
Loading

0 comments on commit 409ad9d

Please sign in to comment.