Current section

42 Versions

Jump to

Compare versions

7 files changed
+108 additions
-21 deletions
  @@ -1,3 +1,8 @@
1 + # 0.1.17
2 + * Attach the method id to the struct as `method_id` (https://github.com/poanetwork/ex_abi/pull/9)
3 + * Add the argument names to the struct as `input_names` (https://github.com/poanetwork/ex_abi/pull/9)
4 + * Add `encode_type/1` to give a public API for encoding single types (used for display in blockscout) (https://github.com/poanetwork/ex_abi/pull/9)
5 + * Add `find_and_decode/2` which finds the correct function selector from the list by method_id and decodes the provided call (https://github.com/poanetwork/ex_abi/pull/9)
1 6 # 0.1.16
2 7 * Allow functions to have mutliple output types (https://github.com/poanetwork/ex_abi/pull/8)
3 8 # 0.1.15
  @@ -10,7 +10,7 @@ by adding `ex_abi` to your list of dependencies in `mix.exs`:
10 10 ```elixir
11 11 def deps do
12 12 [
13 - {:ex_abi, "~> 0.1.16"}
13 + {:ex_abi, "~> 0.1.17"}
14 14 ]
15 15 end
16 16 ```
  @@ -1,7 +1,7 @@
1 1 {<<"app">>,<<"ex_abi">>}.
2 2 {<<"build_tools">>,[<<"mix">>]}.
3 3 {<<"description">>,<<"Ethereum's ABI Interface">>}.
4 - {<<"elixir">>,<<"~> 1.6">>}.
4 + {<<"elixir">>,<<"~> 1.7">>}.
5 5 {<<"files">>,
6 6 [<<"lib">>,<<"lib/abi">>,<<"lib/abi.ex">>,<<"lib/abi/function_selector.ex">>,
7 7 <<"lib/abi/parser.ex">>,<<"lib/abi/type_decoder.ex">>,
  @@ -12,7 +12,6 @@
12 12 <<"src/ethereum_abi_parser.yrl">>]}.
13 13 {<<"licenses">>,[<<"GPL-3.0">>]}.
14 14 {<<"links">>,[{<<"GitHub">>,<<"https://github.com/poanetwork/ex_abi">>}]}.
15 - {<<"maintainers">>,[<<"Ayrat Badykov">>]}.
16 15 {<<"name">>,<<"ex_abi">>}.
17 16 {<<"requirements">>,
18 17 [[{<<"app">>,<<"exth_crypto">>},
  @@ -20,4 +19,4 @@
20 19 {<<"optional">>,false},
21 20 {<<"repository">>,<<"hexpm">>},
22 21 {<<"requirement">>,<<"~> 0.1.4">>}]]}.
23 - {<<"version">>,<<"0.1.16">>}.
22 + {<<"version">>,<<"0.1.17">>}.
  @@ -75,6 +75,34 @@ defmodule ABI do
75 75 ABI.TypeDecoder.decode(data, function_selector)
76 76 end
77 77
78 + @doc """
79 + Finds and decodes the correct function from a list of `ABI.FunctionSelector`s
80 +
81 + The function is found based on the `method_id`, which is generated from the
82 + keccak hash of the function head. More information can be found here:
83 +
84 + https://solidity.readthedocs.io/en/develop/abi-spec.html
85 +
86 + Keep in mind, you must include the method identifier in the passed in data
87 + otherwise this won't work as expected. If you are decoding transaction input data
88 + the identifier is the first four bytes and should already be there.
89 +
90 + ## Examples
91 +
92 + iex> File.read!("priv/dog.abi.json")
93 + ...> |> Poison.decode!
94 + ...> |> ABI.parse_specification
95 + ...> |> ABI.find_and_decode("b85d0bd200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001" |> Base.decode16!(case: :lower))
96 + {%ABI.FunctionSelector{function: "bark", input_names: ["at", "loudly"], method_id: <<184, 93, 11, 210>>, returns: [], types: [:address, :bool]}, [<<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1>>, true]}
97 + """
98 + def find_and_decode(function_selectors, data) do
99 + with {:ok, method_id, rest} <- split_method_id(data),
100 + {:ok, selector} when not is_nil(selector) <-
101 + find_selector_by_method_id(function_selectors, method_id) do
102 + {selector, decode(selector, rest)}
103 + end
104 + end
105 +
78 106 @doc """
79 107 Parses the given ABI specification document into an array of `ABI.FunctionSelector`s.
80 108
  @@ -87,8 +115,8 @@ defmodule ABI do
87 115 iex> File.read!("priv/dog.abi.json")
88 116 ...> |> Poison.decode!
89 117 ...> |> ABI.parse_specification
90 - [%ABI.FunctionSelector{function: "bark", returns: [], types: [:address, :bool]},
91 - %ABI.FunctionSelector{function: "rollover", returns: [:bool], types: []}]
118 + [%ABI.FunctionSelector{function: "bark", input_names: ["at", "loudly"], method_id: <<184, 93, 11, 210>>, returns: [], types: [:address, :bool]},
119 + %ABI.FunctionSelector{function: "rollover", method_id: <<176, 86, 180, 154>>, returns: [:bool], types: []}]
92 120
93 121 iex> [%{
94 122 ...> "constant" => true,
  @@ -103,7 +131,7 @@ defmodule ABI do
103 131 ...> "type" => "function"
104 132 ...> }]
105 133 ...> |> ABI.parse_specification
106 - [%ABI.FunctionSelector{function: "bark", returns: [], types: [:address, :bool]}]
134 + [%ABI.FunctionSelector{function: "bark", method_id: <<184, 93, 11, 210>>, input_names: ["at", "loudly"], returns: [], types: [:address, :bool]}]
107 135
108 136 iex> [%{
109 137 ...> "inputs" => [
  @@ -122,11 +150,32 @@ defmodule ABI do
122 150 ...> "type" => "fallback"
123 151 ...> }]
124 152 ...> |> ABI.parse_specification
125 - [%ABI.FunctionSelector{function: nil, returns: [], types: []}]
153 + [%ABI.FunctionSelector{function: nil, returns: [], types: [], method_id: nil}]
126 154 """
127 155 def parse_specification(doc) do
128 156 doc
129 157 |> Enum.map(&ABI.FunctionSelector.parse_specification_item/1)
130 - |> Enum.filter(& &1)
158 + |> Enum.reject(&is_nil/1)
159 + end
160 +
161 + defp find_selector_by_method_id(function_selectors, method_id_target) do
162 + function_selector =
163 + Enum.find(function_selectors, fn %{method_id: method_id} ->
164 + method_id == method_id_target
165 + end)
166 +
167 + if function_selector do
168 + {:ok, function_selector}
169 + else
170 + {:error, :no_matching_function}
171 + end
172 + end
173 +
174 + defp split_method_id(<<method_id::binary-size(4), rest::binary>>) do
175 + {:ok, method_id, rest}
176 + end
177 +
178 + defp split_method_id(_) do
179 + {:error, :invalid_data}
131 180 end
132 181 end
  @@ -25,11 +25,13 @@ defmodule ABI.FunctionSelector do
25 25 """
26 26 @type t :: %__MODULE__{
27 27 function: String.t(),
28 + method_id: String.t() | nil,
29 + input_names: [String.t()],
28 30 types: [type],
29 31 returns: [type]
30 32 }
31 33
32 - defstruct [:function, types: [], returns: []]
34 + defstruct [:function, :method_id, input_names: [], types: [], returns: []]
33 35
34 36 @doc """
35 37 Decodes a function selector to a struct.
  @@ -136,18 +138,24 @@ defmodule ABI.FunctionSelector do
136 138 } = item
137 139
138 140 input_types = Enum.map(named_inputs, &parse_specification_type/1)
141 + input_names = Enum.map(named_inputs, &Map.get(&1, "name"))
139 142 output_types = Enum.map(named_outputs, &parse_specification_type/1)
140 143
141 - %ABI.FunctionSelector{
144 + selector = %ABI.FunctionSelector{
142 145 function: function_name,
143 146 types: input_types,
144 - returns: output_types
147 + returns: output_types,
148 + input_names: input_names
145 149 }
150 +
151 + add_method_id(selector)
146 152 end
147 153
148 154 def parse_specification_item(%{"type" => "fallback"}) do
149 155 %ABI.FunctionSelector{
150 156 function: nil,
157 + method_id: nil,
158 + input_names: [],
151 159 types: [],
152 160 returns: []
153 161 }
  @@ -181,6 +189,25 @@ defmodule ABI.FunctionSelector do
181 189 ABI.Parser.parse!(single_type, as: :type)
182 190 end
183 191
192 + @doc """
193 + Encodes the given single type as a type-string.
194 +
195 + ## Examples
196 +
197 + iex> ABI.FunctionSelector.encode_type({:uint, 256})
198 + "uint256"
199 +
200 + iex> ABI.FunctionSelector.encode_type({:tuple, [:bool, :address]})
201 + "(bool,address)"
202 +
203 + iex> ABI.FunctionSelector.encode_type({:array, {:array, :address}, 3})
204 + "address[][3]"
205 +
206 + """
207 + def encode_type(single_type) do
208 + get_type(single_type)
209 + end
210 +
184 211 @doc """
185 212 Encodes a function call signature.
186 213
  @@ -204,6 +231,18 @@ defmodule ABI.FunctionSelector do
204 231 "#{function_selector.function}(#{types})"
205 232 end
206 233
234 + defp add_method_id(selector) do
235 + signature = encode(selector)
236 +
237 + case :keccakf1600.hash(:sha3_256, signature) do
238 + <<method_id::binary-size(4), _::binary>> ->
239 + %{selector | method_id: method_id}
240 +
241 + _ ->
242 + selector
243 + end
244 + end
245 +
207 246 defp get_types(function_selector) do
208 247 for type <- function_selector.types do
209 248 get_type(type)
  @@ -255,5 +294,6 @@ defmodule ABI.FunctionSelector do
255 294 defp sanitize_param({key, nil}) when key in ~w(types returns)a do
256 295 {key, []}
257 296 end
297 +
258 298 defp sanitize_param(tuple), do: tuple
259 299 end
Loading more files…