Skip to content

Commit

Permalink
Merge pull request #14 from pthomalla/fix_decode
Browse files Browse the repository at this point in the history
fix decode function with array types
  • Loading branch information
ayrat555 authored Jan 14, 2019
2 parents 6c30623 + 3c434c8 commit c8b562a
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 243 deletions.
14 changes: 9 additions & 5 deletions lib/abi.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ defmodule ABI do
...> |> Base.encode16(case: :lower)
"a291add600000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000001"
iex> ABI.encode("(address[])", [{[]}] ) |> Base.encode16(case: :lower)
"00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000"
iex> ABI.encode("baz(uint8)", [9999])
** (RuntimeError) Data overflow encoding uint, data `9999` cannot fit in 8 bits
Expand Down Expand Up @@ -56,17 +60,17 @@ defmodule ABI do
iex> ABI.decode("baz(uint,address)", "00000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000001" |> Base.decode16!(case: :lower))
[50, <<1::160>>]
iex> ABI.decode("(address[])", "000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000" |> Base.decode16!(case: :lower))
iex> ABI.decode("(address[])", "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000" |> Base.decode16!(case: :lower))
[{[]}]
iex> ABI.decode("(string)", "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000b457468657220546f6b656e000000000000000000000000000000000000000000" |> Base.decode16!(case: :lower))
iex> ABI.decode("(string)", "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000b457468657220546f6b656e000000000000000000000000000000000000000000" |> Base.decode16!(case: :lower))
[{"Ether Token"}]
iex> File.read!("priv/dog.abi.json")
...> |> Poison.decode!
...> |> ABI.parse_specification
...> |> Enum.find(&(&1.function == "bark")) # bark(address,bool)
...> |> ABI.decode("00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001" |> Base.decode16!(case: :lower))
...> |> ABI.decode("b85d0bd200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001" |> Base.decode16!(case: :lower))
[<<1::160>>, true]
"""
def decode(function_signature, data) when is_binary(function_signature) do
Expand Down Expand Up @@ -100,10 +104,10 @@ defmodule ABI do
{%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
with {:ok, method_id, rest} <- Util.split_method_id(data),
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, rest)}
{selector, decode(selector, data)}
end
end

Expand Down
4 changes: 2 additions & 2 deletions lib/abi/event.ex
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ defmodule ABI.Event do

unindexed_arg_types = Enum.map(unindexed_args, &elem(&1, 1))

unindexed_arg_values = ABI.TypeDecoder.decode_raw(data, unindexed_arg_types)
unindexed_arg_values = ABI.TypeDecoder.decode(data, unindexed_arg_types)

{selector, format_event_values(args, indexed_arg_values, unindexed_arg_values)}
end
Expand All @@ -102,7 +102,7 @@ defmodule ABI.Event do
{:dynamic, bytes}
else
topic
|> ABI.TypeDecoder.decode_raw([type])
|> ABI.TypeDecoder.decode([type])
|> List.first()
end

Expand Down
221 changes: 74 additions & 147 deletions lib/abi/type_decoder.ex
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,7 @@ defmodule ABI.TypeDecoder do
[-42]
iex> "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000b68656c6c6f20776f726c64000000000000000000000000000000000000000000"
...> |> Base.decode16!(case: :lower)
iex> ABI.TypeEncoder.encode(["hello world"],[:string])
...> |> ABI.TypeDecoder.decode(
...> %ABI.FunctionSelector{
...> function: nil,
Expand All @@ -67,8 +66,7 @@ defmodule ABI.TypeDecoder do
...> )
[{17, true}]
iex> "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000110000000000000000000000000000000000000000000000000000000000000001"
...> |> Base.decode16!(case: :lower)
iex> ABI.TypeEncoder.encode([[17,1]],[{:array,{:uint,32}}])
...> |> ABI.TypeDecoder.decode(
...> %ABI.FunctionSelector{
...> function: nil,
Expand All @@ -79,8 +77,7 @@ defmodule ABI.TypeDecoder do
...> )
[[17, 1]]
iex> "000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000011020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000110000000000000000000000000000000000000000000000000000000000000001"
...> |> Base.decode16!(case: :lower)
iex> ABI.TypeEncoder.encode([[17, 1], true, <<16, 32>>], [{:array, {:uint, 32}},:bool,{:bytes, 2}])
...> |> ABI.TypeDecoder.decode(
...> %ABI.FunctionSelector{
...> function: nil,
Expand All @@ -93,8 +90,7 @@ defmodule ABI.TypeDecoder do
...> )
[[17, 1], true, <<16, 32>>]
iex> "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000007617765736f6d6500000000000000000000000000000000000000000000000000"
...> |> Base.decode16!(case: :lower)
iex> ABI.TypeEncoder.encode([{"awesome", true}], [{:tuple, [:string, :bool]}])
...> |> ABI.TypeDecoder.decode(
...> %ABI.FunctionSelector{
...> function: nil,
Expand All @@ -105,8 +101,7 @@ defmodule ABI.TypeDecoder do
...> )
[{"awesome", true}]
iex> "000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000"
...> |> Base.decode16!(case: :lower)
iex> ABI.TypeEncoder.encode([{[]}],[{:tuple, [{:array, :address}]}])
...> |> ABI.TypeDecoder.decode(
...> %ABI.FunctionSelector{
...> function: nil,
Expand All @@ -117,8 +112,13 @@ defmodule ABI.TypeDecoder do
...> )
[{[]}]
iex> "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000c556e617574686f72697a656400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000204a2bf2ff0a4eaf1890c8d8679eaa446fb852c4000000000000000000000000861d9af488d5fa485bb08ab6912fff4f7450849a"
...> |> Base.decode16!(case: :lower)
iex> ABI.TypeEncoder.encode( [{
...> "Unauthorized",
...> [
...> 184341788326688649239867304918349890235378717380,
...> 765664983403968947098136133435535343021479462042,
...> ]
...> }], [{:tuple,[:string, {:array, {:uint, 256}}]}])
...> |> ABI.TypeDecoder.decode(
...> %ABI.FunctionSelector{
...> function: nil,
Expand All @@ -136,7 +136,19 @@ defmodule ABI.TypeDecoder do
]
}]
"""

def decode(encoded_data, %FunctionSelector{types: types, method_id: method_id})
when is_binary(method_id) do
{:ok, ^method_id, rest} = ABI.Util.split_method_id(encoded_data)
[result] = decode_raw(rest, [{:tuple, types}])
Tuple.to_list(result)
end

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

def decode(encoded_data, types) do
decode_raw(encoded_data, types)
end

Expand All @@ -146,143 +158,65 @@ defmodule ABI.TypeDecoder do
## Examples
iex> "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000007617765736f6d6500000000000000000000000000000000000000000000000000"
...> |> Base.decode16!(case: :lower)
iex> ABI.TypeEncoder.encode([{"awesome", true}], [{:tuple, [:string, :bool]}])
...> |> ABI.TypeDecoder.decode_raw([{:tuple, [:string, :bool]}])
[{"awesome", true}]
"""
def decode_raw(binary_data, types) do
decode_raw(binary_data, types, 0)
{result, _} = do_decode_raw(binary_data, types)
result
end

defp decode_raw(binary_data, types, initial_cursor_offset) do
{reversed_result, _} =
Enum.reduce(types, {[], initial_cursor_offset}, fn type, {acc, cursor_offset} ->
do_decode_raw(binary_data, type, cursor_offset, acc)
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 | acc], rest}
end)

Enum.reverse(reversed_result)
end

defp do_decode_raw(binary_data, type, cursor_offset, acc) do
{allocation, current_head_bit_length} = type_metadata(type)

<<
_prev::bits-size(cursor_offset),
current_head_data::bits-size(current_head_bit_length),
_rest::binary
>> = binary_data

decoded_data = decode_data(binary_data, type, allocation, current_head_data)

updated_acc = [decoded_data | acc]
next_cursor_offset = cursor_offset + current_head_bit_length

{updated_acc, next_cursor_offset}
end

defp decode_data(binary_data, type, allocation, head_data)

defp decode_data(binary_data, {:tuple, sub_types}, :dynamic, head_data) do
<<data_offset_byte_length::integer-size(256)>> = head_data
data_offset_bit_length = data_offset_byte_length * 8

binary_data
|> decode_raw(sub_types, data_offset_bit_length)
|> List.to_tuple()
end

defp decode_data(_binary_data, {:tuple, sub_types}, :static, head_data) do
head_data
|> decode_raw(sub_types, 0)
|> List.to_tuple()
end

defp decode_data(binary_data, {:array, type}, :dynamic, head_data) do
<<data_offset_byte_length::integer-size(256)>> = head_data

<<
_prev::bytes-size(data_offset_byte_length),
array_length_count::integer-size(256),
_rest::binary
>> = binary_data

if array_length_count > 0 do
types = for _ <- 1..array_length_count, do: type
array_data_offset = data_offset_byte_length * 8 + 256

decode_raw(binary_data, types, array_data_offset)
else
[]
end
{Enum.reverse(reversed_result), binary_rest}
end

defp decode_data(binary_data, {:array, type, count}, :dynamic, head_data) do
repeated_type = for _ <- 1..count, do: type

<<data_offset_byte_length::integer-size(256)>> = head_data
data_offset_bit_length = data_offset_byte_length * 8
@spec decode_type(ABI.FunctionSelector.type(), binary()) :: {any(), binary()}
defp decode_type({:uint, size_in_bits}, data), do: decode_uint(data, size_in_bits)

decode_raw(binary_data, repeated_type, data_offset_bit_length)
end
defp decode_type({:int, size_in_bits}, data), do: decode_int(data, size_in_bits)

defp decode_data(_binary_data, {:array, type, count}, :static, head_data) do
repeated_type = for _ <- 1..count, do: type
decode_raw(head_data, repeated_type, 0)
defp decode_type({:array, type}, data) do
{count, bytes} = decode_uint(data, 256)
decode_type({:array, type, count}, bytes)
end

defp decode_data(binary_data, type, :dynamic, head_data) do
<<data_offset_byte_length::integer-size(256)>> = head_data

<<
_prev::bytes-size(data_offset_byte_length),
type_data::binary
>> = binary_data

{decoded_data, _} = decode_type(type, type_data)

decoded_data
defp decode_type({:array, type, size}, data) do
types = List.duplicate(type, size)
{tuple, bytes} = decode_type({:tuple, types}, data)
{Tuple.to_list(tuple), bytes}
end

defp decode_data(_binary_data, type, :static, head_data) do
{decoded_data, _} = decode_type(type, head_data)

decoded_data
end
defp decode_type({:tuple, types}, data) do
{reversed_result, reversed_dynamic_types, 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}
else
{val, binary} = decode_type(type, binary)
{[val | acc], dynamic, binary}
end
end)

defp type_metadata({:tuple, sub_types}) do
if Enum.any?(sub_types, &FunctionSelector.is_dynamic?/1) do
{:dynamic, 256}
else
{:static, 256 * length(sub_types)}
end
end
{reversed_result_dynamic, binary} =
do_decode_raw(binary, Enum.reverse(reversed_dynamic_types))

defp type_metadata({:array, type, count}) do
if FunctionSelector.is_dynamic?(type) do
{:dynamic, 256}
else
{:static, 256 * count}
end
end

defp type_metadata(type) do
type_head_bit_length = 256
result_dynamic = Enum.reverse(reversed_result_dynamic)

if FunctionSelector.is_dynamic?(type) do
{:dynamic, type_head_bit_length}
else
{:static, type_head_bit_length}
end
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)
end
{result, _} =
Enum.reduce(reversed_result, {[], result_dynamic}, fn
:dynamic, {acc, [value | dynamic]} -> {[value | acc], dynamic}
value, {acc, dynamic} -> {[value | acc], dynamic}
end)

defp decode_type({:int, size_in_bits}, data) do
decode_int(data, size_in_bits)
{List.to_tuple(result), binary}
end

defp decode_type(:address, data), do: decode_bytes(data, 20, :left)
Expand All @@ -299,19 +233,7 @@ defmodule ABI.TypeDecoder do
{value, rest}
end

defp decode_type(:string, data) do
<<
string_length_in_bytes::integer-size(256),
string_data::binary
>> = data

<<
string::bytes-size(string_length_in_bytes),
rest::binary
>> = string_data

{string, rest}
end
defp decode_type(:string, data), do: decode_type(:bytes, data)

defp decode_type(:bytes, data) do
{byte_size, rest} = decode_uint(data, 256)
Expand All @@ -332,22 +254,27 @@ defmodule ABI.TypeDecoder do
defp decode_uint(data, size_in_bits) do
# TODO: Create `left_pad` repo, err, add to `ExthCrypto.Math`
total_bit_size = size_in_bits + ExthCrypto.Math.mod(256 - size_in_bits, 256)

<<value::integer-size(total_bit_size), rest::binary>> = data

{value, rest}
end

defp decode_int(data, _size_in_bits) do
<<value::signed-256, rest::binary>> = 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
# TODO: Create `unright_pad` repo, err, add to `ExthCrypto.Math`
total_size_in_bytes = size_in_bytes + ExthCrypto.Math.mod(32 - size_in_bytes, 32)
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
Expand Down
Loading

0 comments on commit c8b562a

Please sign in to comment.