diff --git a/lib/abi.ex b/lib/abi.ex index df0d5f3..ce84355 100644 --- a/lib/abi.ex +++ b/lib/abi.ex @@ -34,7 +34,7 @@ defmodule ABI do "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000b457468657220546f6b656e000000000000000000000000000000000000000000" iex> File.read!("priv/dog.abi.json") - ...> |> Poison.decode! + ...> |> Jason.decode! ...> |> ABI.parse_specification ...> |> Enum.find(&(&1.function == "bark")) # bark(address,bool) ...> |> ABI.encode([<<1::160>> |> :binary.decode_unsigned, true]) @@ -67,7 +67,7 @@ defmodule ABI do [{"Ether Token"}] iex> File.read!("priv/dog.abi.json") - ...> |> Poison.decode! + ...> |> Jason.decode! ...> |> ABI.parse_specification ...> |> Enum.find(&(&1.function == "bark")) # bark(address,bool) ...> |> ABI.decode("b85d0bd200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001" |> Base.decode16!(case: :lower)) @@ -98,7 +98,7 @@ defmodule ABI do ## Examples iex> File.read!("priv/dog.abi.json") - ...> |> Poison.decode! + ...> |> Jason.decode! ...> |> ABI.parse_specification ...> |> ABI.find_and_decode("b85d0bd200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001" |> Base.decode16!(case: :lower)) {%ABI.FunctionSelector{type: :function, 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]} @@ -116,7 +116,7 @@ defmodule ABI do Non-function entries (e.g. constructors) in the ABI specification are skipped. Fallback function entries are accepted. - This function can be used in combination with a JSON parser, e.g. [`Poison`](https://hex.pm/packages/poison), to parse ABI specification JSON files. + This function can be used in combination with a JSON parser, e.g. [`Jason`](https://hex.pm/packages/Jason), to parse ABI specification JSON files. Opts: @@ -126,7 +126,7 @@ defmodule ABI do ## Examples iex> File.read!("priv/dog.abi.json") - ...> |> Poison.decode! + ...> |> Jason.decode! ...> |> ABI.parse_specification [%ABI.FunctionSelector{type: :function, function: "bark", input_names: ["at", "loudly"], method_id: <<184, 93, 11, 210>>, returns: [], types: [:address, :bool]}, %ABI.FunctionSelector{type: :function, function: "rollover", method_id: <<176, 86, 180, 154>>, returns: [:bool], types: []}] @@ -166,7 +166,7 @@ defmodule ABI do [%ABI.FunctionSelector{type: :function, function: nil, returns: [], types: [], method_id: nil}] iex> File.read!("priv/dog.abi.json") - ...> |> Poison.decode! + ...> |> Jason.decode! ...> |> ABI.parse_specification(include_events?: true) ...> |> Enum.filter(&(&1.type == :event)) [%ABI.FunctionSelector{type: :event, function: "WantsPets", input_names: ["_from_human", "_number", "_belly"], inputs_indexed: [true, false, true], method_id: <<235, 155, 60, 76>>, types: [:string, {:uint, 256}, :bool]}] diff --git a/lib/abi/event.ex b/lib/abi/event.ex index 873f1f9..543f08a 100644 --- a/lib/abi/event.ex +++ b/lib/abi/event.ex @@ -46,7 +46,7 @@ defmodule ABI.Event do # second argument is not, so it is in data ...> data = "0000000000000000000000000000000000000000000000000000000000000000" |> Base.decode16!() ...> File.read!("priv/dog.abi.json") - ...> |> Poison.decode!() + ...> |> Jason.decode!() ...> |> ABI.parse_specification(include_events?: true) ...> |> ABI.Event.find_and_decode(topic1, topic2, topic3, topic4, data) {%ABI.FunctionSelector{ diff --git a/lib/abi/function_selector.ex b/lib/abi/function_selector.ex index 0b7a44d..4fbfd99 100644 --- a/lib/abi/function_selector.ex +++ b/lib/abi/function_selector.ex @@ -9,12 +9,17 @@ defmodule ABI.FunctionSelector do @type type :: {:uint, integer()} | :bool - | :bytes | :string | :address + | :function | {:array, type} | {:array, type, non_neg_integer} | {:tuple, [type]} + | :bytes + | {:bytes, non_neg_integer} + | {:ufixed, non_neg_integer, non_neg_integer} + | {:fixed, non_neg_integer, non_neg_integer} + | {:int, integer} @typedoc """ Struct to represent a function and its input and output types. diff --git a/lib/abi/type_decoder.ex b/lib/abi/type_decoder.ex index c118c82..d0a421d 100644 --- a/lib/abi/type_decoder.ex +++ b/lib/abi/type_decoder.ex @@ -43,7 +43,7 @@ defmodule ABI.TypeDecoder do [-42] - iex> ABI.TypeEncoder.encode(["hello world"],[:string]) + iex> ABI.TypeEncoder.encode(["hello world"],[:string]) ...> |> ABI.TypeDecoder.decode( ...> %ABI.FunctionSelector{ ...> function: nil, @@ -66,7 +66,7 @@ defmodule ABI.TypeDecoder do ...> ) [{17, true}] - iex> ABI.TypeEncoder.encode([[17,1]],[{:array,{:uint,32}}]) + iex> ABI.TypeEncoder.encode([[17,1]],[{:array,{:uint,32}}]) ...> |> ABI.TypeDecoder.decode( ...> %ABI.FunctionSelector{ ...> function: nil, @@ -90,7 +90,7 @@ defmodule ABI.TypeDecoder do ...> ) [[17, 1], true, <<16, 32>>] - iex> ABI.TypeEncoder.encode([{"awesome", true}], [{:tuple, [:string, :bool]}]) + iex> ABI.TypeEncoder.encode([{"awesome", true}], [{:tuple, [:string, :bool]}]) ...> |> ABI.TypeDecoder.decode( ...> %ABI.FunctionSelector{ ...> function: nil, @@ -158,7 +158,7 @@ defmodule ABI.TypeDecoder do ## Examples - iex> ABI.TypeEncoder.encode([{"awesome", true}], [{:tuple, [:string, :bool]}]) + iex> ABI.TypeEncoder.encode([{"awesome", true}], [{:tuple, [:string, :bool]}]) ...> |> ABI.TypeDecoder.decode_raw([{:tuple, [:string, :bool]}]) [{"awesome", true}] """ @@ -177,6 +177,36 @@ defmodule ABI.TypeDecoder do {Enum.reverse(reversed_result), binary_rest} end + # TODO change to ExthCrypto.Math.mod when it's fixed ( mod(-75,32) == 21 ) + def mod(x, n) do + remainder = rem(x, n) + + if (remainder < 0 and n > 0) or (remainder > 0 and n < 0), + do: n + remainder, + else: remainder + end + + @spec decode_bytes(binary(), non_neg_integer(), :left | :right) :: {binary(), binary()} + def decode_bytes(data, size_in_bytes, :left) do + total_size_in_bytes = size_in_bytes + mod(32 - size_in_bytes, 32) + padding_size_in_bytes = total_size_in_bytes - size_in_bytes + + <<_padding::binary-size(padding_size_in_bytes), value::binary-size(size_in_bytes), + rest::binary()>> = data + + {value, rest} + end + + def decode_bytes(data, size_in_bytes, :right) do + total_size_in_bytes = size_in_bytes + mod(32 - size_in_bytes, 32) + padding_size_in_bytes = total_size_in_bytes - size_in_bytes + + <> = data + + {value, rest} + end + @spec decode_type(ABI.FunctionSelector.type(), binary()) :: {any(), binary()} defp decode_type({:uint, size_in_bits}, data), do: decode_uint(data, size_in_bits) @@ -262,33 +292,4 @@ defmodule ABI.TypeDecoder do <> = data {value, rest} end - - # TODO change to ExthCrypto.Math.mod when it's fixed ( mod(-75,32) == 21 ) - def mod(x, n) do - remainder = rem(x, n) - - if (remainder < 0 and n > 0) or (remainder > 0 and n < 0), - do: n + remainder, - else: remainder - end - - @spec decode_bytes(binary(), integer(), atom()) :: {binary(), binary()} - def decode_bytes(data, size_in_bytes, padding_direction) do - total_size_in_bytes = size_in_bytes + mod(32 - size_in_bytes, 32) - padding_size_in_bytes = total_size_in_bytes - size_in_bytes - - case padding_direction do - :left -> - <<_padding::binary-size(padding_size_in_bytes), value::binary-size(size_in_bytes), - rest::binary()>> = data - - {value, rest} - - :right -> - <> = data - - {value, rest} - end - end end diff --git a/lib/abi/type_encoder.ex b/lib/abi/type_encoder.ex index c9f3d17..b3ca2a4 100644 --- a/lib/abi/type_encoder.ex +++ b/lib/abi/type_encoder.ex @@ -307,7 +307,7 @@ defmodule ABI.TypeEncoder do end end - @spec maybe_encode_unsigned(binary() | integer()) :: binary() + @spec maybe_encode_unsigned(binary() | non_neg_integer()) :: binary() defp maybe_encode_unsigned(bin) when is_binary(bin), do: bin defp maybe_encode_unsigned(int) when is_integer(int), do: :binary.encode_unsigned(int) end diff --git a/mix.exs b/mix.exs index 29d3606..0b12f91 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule ABI.Mixfile do def project do [ app: :ex_abi, - version: "0.2.0", + version: "0.2.1", elixir: "~> 1.7", description: "Ethereum's ABI Interface", package: [ @@ -14,7 +14,8 @@ defmodule ABI.Mixfile do ], build_embedded: Mix.env() == :prod, start_permanent: Mix.env() == :prod, - deps: deps() + deps: deps(), + dialyzer: dialyzer() ] end @@ -28,9 +29,16 @@ defmodule ABI.Mixfile do # Run "mix help deps" to learn about dependencies. defp deps do [ + {:dialyxir, "~> 1.0.0-rc.6", only: [:dev, :test], runtime: false}, {:ex_doc, "~> 0.19", only: :dev, runtime: false}, - {:poison, "~> 3.1", only: [:dev, :test]}, + {:jason, "~> 1.1", only: [:dev, :test]}, {:exth_crypto, "~> 0.1.6"} ] end + + defp dialyzer do + [ + flags: [:error_handling, :race_conditions, :underspecs, :unknown, :unmatched_returns] + ] + end end diff --git a/mix.lock b/mix.lock index 701ded6..a43dc79 100644 --- a/mix.lock +++ b/mix.lock @@ -1,13 +1,15 @@ %{ "binary": {:hex, :binary, "0.0.5", "20d816f7274ea34f1b673b4cff2fdb9ebec9391a7a68c349070d515c66b1b2cf", [:mix], [], "hexpm"}, - "earmark": {:hex, :earmark, "1.2.6", "b6da42b3831458d3ecc57314dff3051b080b9b2be88c2e5aa41cd642a5b044ed", [:mix], [], "hexpm"}, - "ex_doc": {:hex, :ex_doc, "0.19.1", "519bb9c19526ca51d326c060cb1778d4a9056b190086a8c6c115828eaccea6cf", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.7", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"}, + "dialyxir": {:hex, :dialyxir, "1.0.0-rc.6", "78e97d9c0ff1b5521dd68041193891aebebce52fc3b93463c0a6806874557d7d", [:mix], [{:erlex, "~> 0.2.1", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm"}, + "earmark": {:hex, :earmark, "1.3.2", "b840562ea3d67795ffbb5bd88940b1bed0ed9fa32834915125ea7d02e35888a5", [:mix], [], "hexpm"}, + "erlex": {:hex, :erlex, "0.2.1", "cee02918660807cbba9a7229cae9b42d1c6143b768c781fa6cee1eaf03ad860b", [:mix], [], "hexpm"}, + "ex_doc": {:hex, :ex_doc, "0.20.2", "1bd0dfb0304bade58beb77f20f21ee3558cc3c753743ae0ddbb0fd7ba2912331", [:mix], [{:earmark, "~> 1.3", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.10", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"}, "exth_crypto": {:hex, :exth_crypto, "0.1.6", "8e636a9bcb75d8e32451be96e547a495121ed2178d078db294edb0f81f7cf2e8", [:mix], [{:binary, "~> 0.0.4", [hex: :binary, repo: "hexpm", optional: false]}, {:keccakf1600, "~> 2.0.0", [hex: :keccakf1600_orig, repo: "hexpm", optional: false]}, {:libsecp256k1, "~> 0.1.9", [hex: :libsecp256k1, repo: "hexpm", optional: false]}], "hexpm"}, + "jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"}, "keccakf1600": {:hex, :keccakf1600_orig, "2.0.0", "0a7217ddb3ee8220d449bbf7575ec39d4e967099f220a91e3dfca4dbaef91963", [:rebar3], [], "hexpm"}, "libsecp256k1": {:hex, :libsecp256k1, "0.1.10", "d27495e2b9851c7765129b76c53b60f5e275bd6ff68292c50536bf6b8d091a4d", [:make, :mix], [{:mix_erlang_tasks, "0.1.0", [hex: :mix_erlang_tasks, repo: "hexpm", optional: false]}], "hexpm"}, - "makeup": {:hex, :makeup, "0.5.5", "9e08dfc45280c5684d771ad58159f718a7b5788596099bdfb0284597d368a882", [:mix], [{:nimble_parsec, "~> 0.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"}, - "makeup_elixir": {:hex, :makeup_elixir, "0.10.0", "0f09c2ddf352887a956d84f8f7e702111122ca32fbbc84c2f0569b8b65cbf7fa", [:mix], [{:makeup, "~> 0.5.5", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"}, + "makeup": {:hex, :makeup, "0.8.0", "9cf32aea71c7fe0a4b2e9246c2c4978f9070257e5c9ce6d4a28ec450a839b55f", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"}, + "makeup_elixir": {:hex, :makeup_elixir, "0.13.0", "be7a477997dcac2e48a9d695ec730b2d22418292675c75aa2d34ba0909dcdeda", [:mix], [{:makeup, "~> 0.8", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"}, "mix_erlang_tasks": {:hex, :mix_erlang_tasks, "0.1.0", "36819fec60b80689eb1380938675af215565a89320a9e29c72c70d97512e4649", [:mix], [], "hexpm"}, - "nimble_parsec": {:hex, :nimble_parsec, "0.4.0", "ee261bb53214943679422be70f1658fff573c5d0b0a1ecd0f18738944f818efe", [:mix], [], "hexpm"}, - "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"}, + "nimble_parsec": {:hex, :nimble_parsec, "0.5.0", "90e2eca3d0266e5c53f8fbe0079694740b9c91b6747f2b7e3c5d21966bba8300", [:mix], [], "hexpm"}, } diff --git a/src/ethereum_abi_parser.yrl b/src/ethereum_abi_parser.yrl index f00521e..691aa44 100644 --- a/src/ethereum_abi_parser.yrl +++ b/src/ethereum_abi_parser.yrl @@ -61,8 +61,8 @@ plain_type(string) -> string; plain_type(bytes) -> bytes; plain_type(int) -> juxt_type(int, 256); plain_type(uint) -> juxt_type(uint, 256); -plain_type(fixed) -> double_juxt_type(fixed, "x", 128, 19); -plain_type(ufixed) -> double_juxt_type(ufixed, "x", 128, 19). +plain_type(fixed) -> double_juxt_type(fixed, x, 128, 19); +plain_type(ufixed) -> double_juxt_type(ufixed, x, 128, 19). with_subscripts(Type, []) -> Type; with_subscripts(Type, [H | T]) -> with_subscripts(with_subscript(Type, H), T).