ex_abi/lib/abi/type_decoder.ex

374 lines
11 KiB
Elixir

defmodule ABI.TypeDecoder do
@moduledoc """
`ABI.TypeDecoder` is responsible for decoding types to the format
expected by Solidity. We generally take a function selector and binary
data and decode that into the original arguments according to the
specification.
"""
alias ABI.FunctionSelector
@doc """
Decodes the given data based on the function selector.
Note, we don't currently try to guess the function name?
## Examples
iex> "00000000000000000000000000000000000000000000000000000000000000450000000000000000000000000000000000000000000000000000000000000001"
...> |> Base.decode16!(case: :lower)
...> |> ABI.TypeDecoder.decode(
...> %ABI.FunctionSelector{
...> function: "baz",
...> types: [
...> {:uint, 32},
...> :bool
...> ],
...> returns: [:bool]
...> }
...> )
[69, true]
iex> "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd6"
...> |> Base.decode16!(case: :lower)
...> |> ABI.TypeDecoder.decode(
...> %ABI.FunctionSelector{
...> function: "baz",
...> types: [
...> {:int, 8}
...> ],
...> returns: [:int]
...> }
...> )
[-42]
iex> ABI.TypeEncoder.encode(["hello world"],[:string])
...> |> ABI.TypeDecoder.decode(
...> %ABI.FunctionSelector{
...> function: nil,
...> types: [
...> :string
...> ]
...> }
...> )
["hello world"]
iex> "00000000000000000000000000000000000000000000000000000000000000110000000000000000000000000000000000000000000000000000000000000001"
...> |> Base.decode16!(case: :lower)
...> |> ABI.TypeDecoder.decode(
...> %ABI.FunctionSelector{
...> function: nil,
...> types: [
...> {:tuple, [{:uint, 32}, :bool]}
...> ]
...> }
...> )
[{17, true}]
iex> ABI.TypeEncoder.encode([[17,1]],[{:array,{:uint,32}}])
...> |> ABI.TypeDecoder.decode(
...> %ABI.FunctionSelector{
...> function: nil,
...> types: [
...> {:array, {:uint, 32}}
...> ]
...> }
...> )
[[17, 1]]
iex> ABI.TypeEncoder.encode([[17, 1], true, <<16, 32>>], [{:array, {:uint, 32}},:bool,{:bytes, 2}])
...> |> ABI.TypeDecoder.decode(
...> %ABI.FunctionSelector{
...> function: nil,
...> types: [
...> {:array, {:uint, 32}},
...> :bool,
...> {:bytes, 2}
...> ]
...> }
...> )
[[17, 1], true, <<16, 32>>]
iex> ABI.TypeEncoder.encode([{"awesome", true}], [{:tuple, [:string, :bool]}])
...> |> ABI.TypeDecoder.decode(
...> %ABI.FunctionSelector{
...> function: nil,
...> types: [
...> {:tuple, [:string, :bool]}
...> ]
...> }
...> )
[{"awesome", true}]
iex> ABI.TypeEncoder.encode([{[]}],[{:tuple, [{:array, :address}]}])
...> |> ABI.TypeDecoder.decode(
...> %ABI.FunctionSelector{
...> function: nil,
...> types: [
...> {:tuple, [{:array, :address}]}
...> ]
...> }
...> )
[{[]}]
iex> ABI.TypeEncoder.encode( [{
...> "Unauthorized",
...> [
...> 184341788326688649239867304918349890235378717380,
...> 765664983403968947098136133435535343021479462042,
...> ]
...> }], [{:tuple,[:string, {:array, {:uint, 256}}]}])
...> |> ABI.TypeDecoder.decode(
...> %ABI.FunctionSelector{
...> function: nil,
...> types: [{:tuple,[
...> :string,
...> {:array, {:uint, 256}}
...> ]}]
...> }
...> )
[{
"Unauthorized",
[
184341788326688649239867304918349890235378717380,
765664983403968947098136133435535343021479462042,
]
}]
"""
def decode(encoded_data, selector_or_types, data_type \\ :input)
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}, :input) do
decode(encoded_data, types)
end
def decode(encoded_data, %FunctionSelector{returns: types, method_id: method_id}, :output)
when is_binary(method_id) do
case ABI.Util.split_method_id(encoded_data) do
{:ok, ^method_id, rest} -> decode_raw(rest, types)
_ -> decode_raw(encoded_data, types)
end
end
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
@doc """
Similar to `ABI.TypeDecoder.decode/2` except accepts a list of types instead
of a function selector.
## Examples
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
{result, _} = do_decode_raw(binary_data, types)
result
end
def do_decode_raw(binary_data, types) do
{reversed_result, binary_rest} =
Enum.reduce(types, {[], binary_data}, fn type, {acc, binary} ->
{value, rest} =
if FunctionSelector.dynamic?(type) do
decode_type(type, binary, binary_data)
else
decode_type(type, binary)
end
{[value | acc], rest}
end)
{Enum.reverse(reversed_result), binary_rest}
end
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) ::
{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
@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),
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
defp decode_type({:uint, size_in_bits}, data) do
decode_uint(data, size_in_bits)
end
defp decode_type({:int, size_in_bits}, data) do
decode_int(data, size_in_bits)
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
types = List.duplicate(type, size)
{tuple, bytes} = decode_type({:tuple, types}, data)
{Tuple.to_list(tuple), bytes}
end
defp decode_type({:tuple, types}, data) do
{reversed_result, _, binary} =
Enum.reduce(types, {[], [], data}, fn type, {acc, dynamic, binary} ->
if FunctionSelector.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(:address, data), do: decode_bytes(data, 20, :left)
defp decode_type(:bool, data) do
{encoded_value, rest} = decode_uint(data, 8)
value =
case encoded_value do
1 -> true
0 -> false
end
{value, rest}
end
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)
types = List.duplicate(type, count)
{tuple, _bytes} = decode_type({:tuple, types}, bytes)
{Tuple.to_list(tuple), rest_bytes}
end
defp decode_type({:array, type, size}, data, full_data) do
{offset, rest_bytes} = decode_uint(data, 256)
<<_padding::binary-size(offset), rest_data::binary>> = full_data
types = List.duplicate(type, size)
{tuple, _} = decode_type({:tuple, types}, rest_data)
{Tuple.to_list(tuple), rest_bytes}
end
defp decode_type({:tuple, types}, data, full_data) do
{offset, rest_bytes} = decode_uint(data, 256)
<<_padding::binary-size(offset), tuple_data::binary>> = full_data
{reversed_result, _, _binary} =
Enum.reduce(types, {[], [], tuple_data}, fn type, {acc, dynamic, binary} ->
if FunctionSelector.dynamic?(type) do
{val, binary} = decode_type(type, binary, tuple_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), rest_bytes}
end
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
defp decode_uint(data, size_in_bits) do
total_bit_size = size_in_bits + 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
end