Skip to content

Commit

Permalink
Merge pull request #24 from poanetwork/vb-3.0
Browse files Browse the repository at this point in the history
Support encoding/decoding of dynamic size types
  • Loading branch information
vbaranov authored Mar 19, 2020
2 parents cd8eb41 + 4c87f55 commit 02539ef
Show file tree
Hide file tree
Showing 8 changed files with 577 additions and 179 deletions.
4 changes: 2 additions & 2 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
elixir 1.9.0
erlang 21.0.4
elixir 1.9.4
erlang 22
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# 0.3.0
* Fix encoding/decoding of dynamic size types (https://github.com/poanetwork/ex_abi/pull/24)
# 0.2.2
* Add support for constructor selectors (https://github.com/poanetwork/ex_abi/pull/21)
# 0.2.1
Expand Down
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ by adding `ex_abi` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:ex_abi, "~> 0.2.0"}
{:ex_abi, "~> 0.3.0"}
]
end
```
Expand All @@ -27,7 +27,9 @@ To encode a function call, pass the ABI spec and the data to pass in to `ABI.enc

```elixir
iex> ABI.encode("baz(uint,address)", [50, <<1::160>> |> :binary.decode_unsigned])
<<162, 145, 173, 214, 0, 0, 0, 0, 0, 0, 0, 0, ...>
<<162, 145, 173, 214, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, ...>>
```

Then, you can construct an Ethereum transaction with that data, e.g.
Expand All @@ -47,8 +49,8 @@ That transaction can then be sent via JSON-RPC or DevP2P to execute the given fu
Decode is generally the opposite of encoding, though we generally leave off the function signature from the start of the data. E.g. from above:

```elixir
iex> ABI.decode("baz(uint,address)", "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000001" |> Base.decode16!(case: :lower))
[50, <<1::160>> |> :binary.decode_unsigned]
iex> ABI.decode("baz(uint,address)", "00000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000001" |> Base.decode16!(case: :lower))
[50, <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1>>]
```

## Support
Expand Down
129 changes: 104 additions & 25 deletions lib/abi/type_decoder.ex
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,13 @@ defmodule ABI.TypeDecoder do
def do_decode_raw(binary_data, types) do
{reversed_result, binary_rest} =
Enum.reduce(types, {[], binary_data}, fn type, {acc, binary} ->
{value, rest} = decode_type(type, binary)
{value, rest} =
if ABI.FunctionSelector.is_dynamic?(type) do
decode_type(type, binary, binary_data)
else
decode_type(type, binary)
end

{[value | acc], rest}
end)

Expand All @@ -186,7 +192,8 @@ defmodule ABI.TypeDecoder do
else: remainder
end

@spec decode_bytes(binary(), non_neg_integer(), :left | :right) :: {binary(), binary()}
@spec decode_bytes(binary(), non_neg_integer(), :left) ::
{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
Expand All @@ -197,24 +204,53 @@ defmodule ABI.TypeDecoder do
{value, rest}
end

def decode_bytes(data, size_in_bytes, :right) do
@spec decode_bytes(binary(), non_neg_integer(), :right, binary(), binary()) ::
{binary(), binary()}
def decode_bytes(_, size_in_bytes, :right, full_data, _) 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

<<value::binary-size(size_in_bytes), _padding::binary-size(padding_size_in_bytes),
rest::binary()>> = data
rest2::binary()>> = full_data

{value, rest2}
end

@spec decode_bytes(binary(), non_neg_integer(), :right, binary()) ::
{binary(), binary()}
def decode_bytes(data, size_in_bytes, :right, rest) 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

<<value::binary-size(size_in_bytes), _padding::binary-size(padding_size_in_bytes),
_rest::binary()>> = 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)
@spec decode_type(ABI.FunctionSelector.type(), binary(), binary()) ::
{any(), binary(), binary()}
defp decode_type({:uint, size_in_bits}, data) do
{value, rest} = decode_uint(data, size_in_bits)
{value, rest}
end

defp decode_type({:int, size_in_bits}, data), do: decode_int(data, size_in_bits)
defp decode_type({:int, size_in_bits}, data) do
{value, rest} = decode_int(data, size_in_bits)
{value, rest}
end

defp decode_type({:array, type}, data) do
{count, bytes} = decode_uint(data, 256)
decode_type({:array, type, count}, bytes)
{offset, rest_bytes} = decode_uint(data, 256)
<<_padding::binary-size(offset), rest_data::binary>> = data
{count, bytes} = decode_uint(rest_data, 256)
array_elements_bytes = 32 * count
<<final_bytes::binary-size(array_elements_bytes), _rest_data::binary>> = bytes
decode_type({:array, type, count}, final_bytes, rest_bytes)
end

defp decode_type({:bytes, size}, data) when size > 0 and size <= 32 do
decode_bytes(data, size, :right, data, data)
end

defp decode_type({:array, type, size}, data) do
Expand All @@ -223,26 +259,27 @@ defmodule ABI.TypeDecoder do
{Tuple.to_list(tuple), bytes}
end

defp decode_type(:bytes, data) do
{byte_size, bytes} = decode_uint(data, 256)
decode_bytes(bytes, byte_size, :right, <<>>)
end

defp decode_type({:tuple, types}, data) do
{reversed_result, reversed_dynamic_types, binary} =
{reversed_result, _, binary} =
Enum.reduce(types, {[], [], data}, fn type, {acc, dynamic, binary} ->
if ABI.FunctionSelector.is_dynamic?(type) do
{_, binary} = decode_uint(binary, 256)
{[:dynamic | acc], [type | dynamic], binary}
{val, binary} = decode_type(type, binary, data)
{[val | acc], [type | dynamic], binary}
else
{val, binary} = decode_type(type, binary)
{[val | acc], dynamic, binary}
end
end)

{reversed_result_dynamic, binary} =
do_decode_raw(binary, Enum.reverse(reversed_dynamic_types))

result_dynamic = Enum.reverse(reversed_result_dynamic)
result_dynamic = []

{result, _} =
Enum.reduce(reversed_result, {[], result_dynamic}, fn
:dynamic, {acc, [value | dynamic]} -> {[value | acc], dynamic}
value, {acc, dynamic} -> {[value | acc], dynamic}
end)

Expand All @@ -263,20 +300,62 @@ defmodule ABI.TypeDecoder do
{value, rest}
end

defp decode_type(:string, data), do: decode_type(:bytes, data)
defp decode_type(:string, data) do
decode_type(:bytes, data)
end

defp decode_type(:bytes, data) do
{byte_size, rest} = decode_uint(data, 256)
decode_bytes(rest, byte_size, :right)
defp decode_type({:array, type}, data, full_data) do
{offset, rest_bytes} = decode_uint(data, 256)
<<_padding::binary-size(offset), rest_data::binary>> = full_data
{count, bytes} = decode_uint(rest_data, 256)
array_elements_bytes = 32 * count
<<final_bytes::binary-size(array_elements_bytes), _rest_data::binary>> = bytes
decode_type({:array, type, count}, final_bytes, rest_bytes)
end

defp decode_type({:bytes, 0}, data), do: {<<>>, data}
defp decode_type({:array, type, size}, data, full_data) do
types = List.duplicate(type, size)
{tuple, _} = decode_type({:tuple, types}, data, full_data)
{Tuple.to_list(tuple), full_data}
end

defp decode_type({:bytes, size}, data) when size > 0 and size <= 32 do
decode_bytes(data, size, :right)
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
{val, binary} = decode_type(type, binary, data)
{[val | acc], [type | dynamic], binary}
else
{val, binary} = decode_type(type, binary)
{[val | acc], dynamic, binary}
end
end)

result_dynamic = []

{result, _} =
Enum.reduce(reversed_result, {[], result_dynamic}, fn
value, {acc, dynamic} -> {[value | acc], dynamic}
end)

{List.to_tuple(result), binary}
end

defp decode_type(els, _) do
defp decode_type(:string, data, full_data) do
decode_type(:bytes, data, full_data)
end

defp decode_type(:bytes, data, full_data) do
{offset, rest} = decode_uint(data, 256)
<<_padding::binary-size(offset), rest_data::binary>> = full_data
{byte_size, dynamic_length_data} = decode_uint(rest_data, 256)
decode_bytes(dynamic_length_data, byte_size, :right, rest)
end

defp decode_type({:bytes, 0}, data, _),
do: {<<>>, data}

defp decode_type(els, _, _) do
raise "Unsupported decoding type: #{inspect(els)}"
end

Expand Down
Loading

0 comments on commit 02539ef

Please sign in to comment.