Skip to content

Commit

Permalink
Merge pull request #36 from ayrat555/ayrat555/decode-outputs
Browse files Browse the repository at this point in the history
Decode outputs
  • Loading branch information
vbaranov authored May 28, 2020
2 parents 87c62cc + 1c28d2f commit 1a525e5
Show file tree
Hide file tree
Showing 10 changed files with 219 additions and 178 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# 0.4.0
* Fix encoding and decoding of dynamic types (https://github.com/poanetwork/ex_abi/pull/34)
* Allow to decoded function outputs (https://github.com/poanetwork/ex_abi/pull/36)
# 0.3.2
* Fix array/tuple decoding (https://github.com/poanetwork/ex_abi/pull/32)
# 0.3.1
Expand Down
13 changes: 1 addition & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,8 @@ iex> ABI.encode("baz(uint,address)", [50, <<1::160>> |> :binary.decode_unsigned]
0, ...>>
```

Then, you can construct an Ethereum transaction with that data, e.g.
That transaction can then be sent via JSON-RPC Client [ethereumex](https://github.com/mana-ethereum/ethereumex).

```elixir
# Blockchain comes from `Exthereum.Blockchain`, see below.
iex> %Blockchain.Transaction{
...> # ...
...> data: <<162, 145, 173, 214, 0, 0, 0, 0, 0, 0, 0, 0, ...>
...> }
```

That transaction can then be sent via JSON-RPC or DevP2P to execute the given function.

### Decoding

Expand Down Expand Up @@ -78,5 +69,3 @@ Currently supports:

* [Solidity ABI](https://solidity.readthedocs.io/en/develop/abi-spec.html)
* [Solidity Docs](https://solidity.readthedocs.io/)
* [Solidity Grammar](https://github.com/ethereum/solidity/blob/develop/docs/grammar.txt)
* [Exthereum Blockchain](https://github.com/exthereum/blockchain)
36 changes: 23 additions & 13 deletions lib/abi.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ defmodule ABI do
it to or from types that Solidity understands.
"""

alias ABI.Util
alias ABI.{FunctionSelector, Parser, TypeEncoder, TypeDecoder, Util}

@doc """
Encodes the given data into the function signature or tuple signature.
Expand Down Expand Up @@ -44,12 +44,17 @@ defmodule ABI do
...> |> Base.encode16(case: :lower)
"b85d0bd200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001"
"""
def encode(function_signature, data) when is_binary(function_signature) do
encode(ABI.Parser.parse!(function_signature), data)

def encode(function_signature, data, data_type \\ :input)

def encode(function_signature, data, data_type) when is_binary(function_signature) do
function_signature
|> Parser.parse!()
|> encode(data, data_type)
end

def encode(%ABI.FunctionSelector{} = function_selector, data) do
ABI.TypeEncoder.encode(data, function_selector)
def encode(%FunctionSelector{} = function_selector, data, data_type) do
TypeEncoder.encode(data, function_selector, data_type)
end

@doc """
Expand All @@ -76,12 +81,17 @@ defmodule ABI do
...> |> ABI.decode("b85d0bd200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001" |> Base.decode16!(case: :lower))
[<<1::160>>, true]
"""
def decode(function_signature, data) when is_binary(function_signature) do
decode(ABI.Parser.parse!(function_signature), data)

def decode(function_signature, data, data_type \\ :input)

def decode(function_signature, data, data_type) when is_binary(function_signature) do
function_signature
|> Parser.parse!()
|> decode(data, data_type)
end

def decode(%ABI.FunctionSelector{} = function_selector, data) do
ABI.TypeDecoder.decode(data, function_selector)
def decode(%FunctionSelector{} = function_selector, data, data_type) do
TypeDecoder.decode(data, function_selector, data_type)
end

@doc """
Expand All @@ -106,11 +116,11 @@ defmodule ABI do
...> |> 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]}
"""
def find_and_decode(function_selectors, data) do
def find_and_decode(function_selectors, data, data_type \\ :input) do
with {:ok, method_id, _rest} <- Util.split_method_id(data),
{:ok, selector} when not is_nil(selector) <-
Util.find_selector_by_method_id(function_selectors, method_id) do
{selector, decode(selector, data)}
{selector, decode(selector, data, data_type)}
end
end

Expand Down Expand Up @@ -181,11 +191,11 @@ defmodule ABI do
def parse_specification(doc, opts \\ []) do
if opts[:include_events?] do
doc
|> Enum.map(&ABI.FunctionSelector.parse_specification_item/1)
|> Enum.map(&FunctionSelector.parse_specification_item/1)
|> Enum.reject(&is_nil/1)
else
doc
|> Enum.map(&ABI.FunctionSelector.parse_specification_item/1)
|> Enum.map(&FunctionSelector.parse_specification_item/1)
|> Enum.reject(&is_nil/1)
|> Enum.reject(&(&1.type == :event))
end
Expand Down
19 changes: 12 additions & 7 deletions lib/abi/type_decoder.ex
Original file line number Diff line number Diff line change
Expand Up @@ -136,19 +136,24 @@ defmodule ABI.TypeDecoder do
]
}]
"""
def decode(encoded_data, selector_or_types, data_type \\ :input)

def decode(encoded_data, %FunctionSelector{types: types, method_id: method_id})
def decode(encoded_data, %FunctionSelector{types: types, method_id: method_id}, :input)
when is_binary(method_id) do
{:ok, ^method_id, rest} = ABI.Util.split_method_id(encoded_data)

decode_raw(rest, types)
end

def decode(encoded_data, %FunctionSelector{types: types}) do
def decode(encoded_data, %FunctionSelector{types: types}, :input) do
decode(encoded_data, types)
end

def decode(encoded_data, types) do
def decode(encoded_data, %FunctionSelector{returns: types}, :output) do
decode(encoded_data, types)
end

def decode(encoded_data, types, _) when is_list(types) do
decode_raw(encoded_data, types)
end

Expand All @@ -171,7 +176,7 @@ defmodule ABI.TypeDecoder do
{reversed_result, binary_rest} =
Enum.reduce(types, {[], binary_data}, fn type, {acc, binary} ->
{value, rest} =
if ABI.FunctionSelector.is_dynamic?(type) do
if FunctionSelector.is_dynamic?(type) do
decode_type(type, binary, binary_data)
else
decode_type(type, binary)
Expand Down Expand Up @@ -228,7 +233,7 @@ defmodule ABI.TypeDecoder do
{value, rest}
end

@spec decode_type(ABI.FunctionSelector.type(), binary(), binary()) ::
@spec decode_type(FunctionSelector.type(), binary(), binary()) ::
{any(), binary(), binary()}
defp decode_type({:uint, size_in_bits}, data) do
decode_uint(data, size_in_bits)
Expand All @@ -252,7 +257,7 @@ defmodule ABI.TypeDecoder do
defp decode_type({:tuple, types}, data) do
{reversed_result, _, binary} =
Enum.reduce(types, {[], [], data}, fn type, {acc, dynamic, binary} ->
if ABI.FunctionSelector.is_dynamic?(type) do
if FunctionSelector.is_dynamic?(type) do
{val, binary} = decode_type(type, binary, data)
{[val | acc], [type | dynamic], binary}
else
Expand Down Expand Up @@ -317,7 +322,7 @@ defmodule ABI.TypeDecoder do

{reversed_result, _, _binary} =
Enum.reduce(types, {[], [], tuple_data}, fn type, {acc, dynamic, binary} ->
if ABI.FunctionSelector.is_dynamic?(type) do
if FunctionSelector.is_dynamic?(type) do
{val, binary} = decode_type(type, binary, tuple_data)
{[val | acc], [type | dynamic], binary}
else
Expand Down
45 changes: 26 additions & 19 deletions lib/abi/type_encoder.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,39 @@ defmodule ABI.TypeEncoder do
array of data and encode that array according to the specification.
"""

alias ABI.FunctionSelector

@doc """
Encodes the given data based on the function selector.
"""

def encode(data, %ABI.FunctionSelector{function: nil, types: types}) do
def encode(data, selector_or_types, data_type \\ :input)

def encode(data, %FunctionSelector{function: nil, types: types}, :input) do
do_encode(data, types)
end

def encode(data, %ABI.FunctionSelector{types: types} = function_selector) do
def encode(data, %FunctionSelector{types: types} = function_selector, :input) do
encode_method_id(function_selector) <> do_encode(data, types)
end

def encode(data, types) do
def encode(data, %FunctionSelector{returns: types}, :output) do
do_encode(data, types)
end

def encode_raw(data, types) do
def encode(data, types, _) when is_list(types) do
do_encode(data, types)
end

def do_encode(params, types, static_acc \\ [], dynamic_acc \\ [])
def encode_raw(data, types, _) when is_list(types) do
do_encode(data, types)
end

def do_encode([], [], reversed_static_acc, reversed_dynamic_acc) do
static_acc = reversed_static_acc |> Enum.reverse()
defp do_encode(params, types, static_acc \\ [], dynamic_acc \\ [])

dynamic_acc = reversed_dynamic_acc |> Enum.reverse()
defp do_encode([], [], reversed_static_acc, reversed_dynamic_acc) do
static_acc = Enum.reverse(reversed_static_acc)
dynamic_acc = Enum.reverse(reversed_dynamic_acc)

static_part_size =
Enum.reduce(static_acc, 0, fn value, acc ->
Expand Down Expand Up @@ -66,12 +73,12 @@ defmodule ABI.TypeEncoder do
end)
end

def do_encode(
[current_parameter | remaining_parameters],
[current_type | remaining_types],
static_acc,
dynamic_acc
) do
defp do_encode(
[current_parameter | remaining_parameters],
[current_type | remaining_types],
static_acc,
dynamic_acc
) do
{new_static_acc, new_dynamic_acc} =
do_encode_type(current_type, current_parameter, static_acc, dynamic_acc)

Expand Down Expand Up @@ -156,7 +163,7 @@ defmodule ABI.TypeEncoder do
types = List.duplicate(type, size)
result = do_encode(data, types)

if ABI.FunctionSelector.is_dynamic?(type) do
if FunctionSelector.is_dynamic?(type) do
data_bytes_size = byte_size(result)

{[{:dynamic, data_bytes_size} | static_acc], [result | dynamic_acc]}
Expand All @@ -175,7 +182,7 @@ defmodule ABI.TypeEncoder do

result = do_encode(list_parameters, types)

if ABI.FunctionSelector.is_dynamic?(type) do
if FunctionSelector.is_dynamic?(type) do
data_bytes_size = byte_size(result)

{[{:dynamic, data_bytes_size} | static_acc], [result | dynamic_acc]}
Expand All @@ -188,14 +195,14 @@ defmodule ABI.TypeEncoder do
pad(bytes, byte_size(bytes), :right)
end

@spec encode_method_id(%ABI.FunctionSelector{}) :: binary()
defp encode_method_id(%ABI.FunctionSelector{function: nil}), do: ""
@spec encode_method_id(%FunctionSelector{}) :: binary()
defp encode_method_id(%FunctionSelector{function: nil}), do: ""

defp encode_method_id(function_selector) do
# Encode selector e.g. "baz(uint32,bool)" and take keccak
kec =
function_selector
|> ABI.FunctionSelector.encode()
|> FunctionSelector.encode()
|> ExthCrypto.Hash.Keccak.kec()

# Take first four bytes
Expand Down
1 change: 1 addition & 0 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ defmodule ABI.Mixfile do
# Run "mix help deps" to learn about dependencies.
defp deps do
[
{:credo, "~> 1.4", only: [:dev, :test], runtime: false},
{:dialyxir, "~> 1.0.0", only: [:dev, :test], runtime: false},
{:ex_doc, "~> 0.21", only: :dev, runtime: false},
{:jason, "~> 1.2", only: [:dev, :test]},
Expand Down
2 changes: 2 additions & 0 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
%{
"binary": {:hex, :binary, "0.0.5", "20d816f7274ea34f1b673b4cff2fdb9ebec9391a7a68c349070d515c66b1b2cf", [:mix], [], "hexpm", "ee1e9ebcab703a4e24db554957fbb540642fe9327eb9e295cb3f07dd7c11ddb2"},
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"},
"credo": {:hex, :credo, "1.4.0", "92339d4cbadd1e88b5ee43d427b639b68a11071b6f73854e33638e30a0ea11f5", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "1fd3b70dce216574ce3c18bdf510b57e7c4c85c2ec9cad4bff854abaf7e58658"},
"dialyxir": {:hex, :dialyxir, "1.0.0", "6a1fa629f7881a9f5aaf3a78f094b2a51a0357c843871b8bc98824e7342d00a5", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "aeb06588145fac14ca08d8061a142d52753dbc2cf7f0d00fc1013f53f8654654"},
"earmark": {:hex, :earmark, "1.4.4", "4821b8d05cda507189d51f2caeef370cf1e18ca5d7dfb7d31e9cafe6688106a4", [:mix], [], "hexpm", "1f93aba7340574847c0f609da787f0d79efcab51b044bb6e242cae5aca9d264d"},
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
Expand Down
11 changes: 5 additions & 6 deletions test/abi/type_decoder_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ defmodule ABI.TypeDecoderTest do

doctest ABI.TypeDecoder

alias ABI.TypeDecoder
alias ABI.TypeEncoder
alias ABI.{TypeDecoder, TypeEncoder, FunctionSelector}

describe "decode/2 '{:int, size}' type" do
test "successfully decodes positives and negatives integers" do
Expand All @@ -14,7 +13,7 @@ defmodule ABI.TypeDecoderTest do
result_to_decode =
<<199, 158, 242, 32>> <> Base.decode16!(positive_int <> negative_int, case: :lower)

selector = %ABI.FunctionSelector{
selector = %FunctionSelector{
function: "baz",
method_id: <<199, 158, 242, 32>>,
types: [
Expand All @@ -25,9 +24,9 @@ defmodule ABI.TypeDecoderTest do
}

result = [42, -9999]
assert ABI.TypeDecoder.decode(result_to_decode, selector) == result
assert TypeDecoder.decode(result_to_decode, selector) == result

assert ABI.TypeEncoder.encode(result, selector) ==
assert TypeEncoder.encode(result, selector) ==
result_to_decode
end
end
Expand Down Expand Up @@ -558,7 +557,7 @@ defmodule ABI.TypeDecoderTest do

res =
encoded_pattern
|> ABI.TypeDecoder.decode(%ABI.FunctionSelector{
|> TypeDecoder.decode(%FunctionSelector{
function: nil,
types: [
{:tuple, [:string, {:uint, 256}]}
Expand Down
Loading

0 comments on commit 1a525e5

Please sign in to comment.