Current section
42 Versions
Jump to
Current section
42 Versions
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…