format code

This commit is contained in:
Ayrat Badykov 2018-06-22 11:15:20 +03:00
parent d6ccbf0bf3
commit 929ba1570d
13 changed files with 136 additions and 117 deletions

5
.formatter.exs Normal file
View File

@ -0,0 +1,5 @@
[
inputs: [
"{lib,config,test}/**/*.{ex,exs}"
]
]

View File

@ -1,7 +0,0 @@
Copyright 2018 Geoffrey Hayes
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1,16 +1,16 @@
# ABI
# ExABI
The [Application Binary Interface](https://solidity.readthedocs.io/en/develop/abi-spec.html) (ABI) of Solidity describes how to transform binary data to types which the Solidity programming language understands. For instance, if we want to call a function `bark(uint32,bool)` on a Solidity-created contract `contract Dog`, what `data` parameter do we pass into our Ethereum transaction? This project allows us to encode such function calls.
## Installation
If [available in Hex](https://hex.pm/docs/publish), the package can be installed
by adding `abi` to your list of dependencies in `mix.exs`:
by adding `ex_abi` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:abi, "~> 0.1.8"}
{:ex_abi, "~> 0.1.12"}
]
end
```
@ -77,7 +77,3 @@ Currently supports:
* [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)
# Collaboration
This ABI library is licensed under the MIT license. Feel free to submit issues, pull requests or fork the code as you wish.

View File

@ -38,6 +38,7 @@ defmodule ABI do
def encode(function_signature, data) when is_binary(function_signature) do
encode(ABI.Parser.parse!(function_signature), data)
end
def encode(%ABI.FunctionSelector{} = function_selector, data) do
ABI.TypeEncoder.encode(data, function_selector)
end
@ -69,6 +70,7 @@ defmodule ABI do
def decode(function_signature, data) when is_binary(function_signature) do
decode(ABI.Parser.parse!(function_signature), data)
end
def decode(%ABI.FunctionSelector{} = function_selector, data) do
ABI.TypeDecoder.decode(data, function_selector)
end
@ -125,6 +127,6 @@ defmodule ABI do
def parse_specification(doc) do
doc
|> Enum.map(&ABI.FunctionSelector.parse_specification_item/1)
|> Enum.filter(&(&1))
|> Enum.filter(& &1)
end
end

View File

@ -7,20 +7,20 @@ defmodule ABI.FunctionSelector do
require Integer
@type type ::
{:uint, integer()} |
:bool |
:bytes |
:string |
:address |
{:array, type} |
{:array, type, non_neg_integer} |
{:tuple, [type]}
{:uint, integer()}
| :bool
| :bytes
| :string
| :address
| {:array, type}
| {:array, type, non_neg_integer}
| {:tuple, [type]}
@type t :: %__MODULE__{
function: String.t,
types: [type],
returns: type
}
function: String.t(),
types: [type],
returns: type
}
defstruct [:function, :types, :returns]
@ -129,6 +129,7 @@ defmodule ABI.FunctionSelector do
returns: List.first(output_types)
}
end
def parse_specification_item(%{"type" => "fallback"}) do
%ABI.FunctionSelector{
function: nil,
@ -136,6 +137,7 @@ defmodule ABI.FunctionSelector do
returns: nil
}
end
def parse_specification_item(_), do: nil
defp parse_specification_type(%{"type" => type}), do: decode_type(type)
@ -208,15 +210,14 @@ defmodule ABI.FunctionSelector do
"(#{Enum.join(encoded_types, ",")})"
end
defp get_type(els), do: raise "Unsupported type: #{inspect els}"
defp get_type(els), do: raise("Unsupported type: #{inspect(els)}")
@doc false
@spec is_dynamic?(ABI.FunctionSelector.type) :: boolean
@spec is_dynamic?(ABI.FunctionSelector.type()) :: boolean
def is_dynamic?(:bytes), do: true
def is_dynamic?(:string), do: true
def is_dynamic?({:array, _type}), do: true
def is_dynamic?({:array, type, len}) when len > 0, do: is_dynamic?(type)
def is_dynamic?({:tuple, types}), do: Enum.any?(types, &is_dynamic?/1)
def is_dynamic?(_), do: false
end

View File

@ -3,13 +3,14 @@ defmodule ABI.Parser do
@doc false
def parse!(str, opts \\ []) do
{:ok, tokens, _} = str |> String.to_charlist |> :ethereum_abi_lexer.string
{:ok, tokens, _} = str |> String.to_charlist() |> :ethereum_abi_lexer.string()
tokens = case opts[:as] do
nil -> tokens
:type -> [{:"expecting type", 1} | tokens]
:selector -> [{:"expecting selector", 1} | tokens]
end
tokens =
case opts[:as] do
nil -> tokens
:type -> [{:"expecting type", 1} | tokens]
:selector -> [{:"expecting selector", 1} | tokens]
end
{:ok, ast} = :ethereum_abi_parser.parse(tokens)

View File

@ -151,16 +151,19 @@ defmodule ABI.TypeDecoder do
do_decode(types, encoded_data, [])
end
@spec do_decode([ABI.FunctionSelector.type], binary(), [any()]) :: [any()]
defp do_decode([], bin, _) when byte_size(bin) > 0, do: raise("Found extra binary data: #{inspect bin}")
@spec do_decode([ABI.FunctionSelector.type()], binary(), [any()]) :: [any()]
defp do_decode([], bin, _) when byte_size(bin) > 0,
do: raise("Found extra binary data: #{inspect(bin)}")
defp do_decode([], _, acc), do: Enum.reverse(acc)
defp do_decode([type|remaining_types], data, acc) do
defp do_decode([type | remaining_types], data, acc) do
{decoded, remaining_data} = decode_type(type, data)
do_decode(remaining_types, remaining_data, [decoded | acc])
end
@spec decode_type(ABI.FunctionSelector.type, binary()) :: {any(), binary()}
@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
@ -170,10 +173,11 @@ defmodule ABI.TypeDecoder do
defp decode_type(:bool, data) do
{encoded_value, rest} = decode_uint(data, 8)
value = case encoded_value do
1 -> true
0 -> false
end
value =
case encoded_value do
1 -> true
0 -> false
end
{value, rest}
end
@ -190,6 +194,7 @@ defmodule ABI.TypeDecoder do
end
defp decode_type({:bytes, 0}, data), do: {<<>>, data}
defp decode_type({:bytes, size}, data) when size > 0 and size <= 32 do
decode_bytes(data, size, :right)
end
@ -200,45 +205,49 @@ defmodule ABI.TypeDecoder do
end
defp decode_type({:array, _type, 0}, data), do: {[], data}
defp decode_type({:array, type, element_count}, data) do
repeated_type = Enum.map(1..element_count, fn _ -> type end)
{tuple, rest} = decode_type({:tuple, repeated_type}, data)
{tuple |> Tuple.to_list, rest}
{tuple |> Tuple.to_list(), rest}
end
defp decode_type({:tuple, types}, starting_data) do
# First pass, decode static types
{elements, rest} = Enum.reduce(types, {[], starting_data}, fn type, {elements, data} ->
if ABI.FunctionSelector.is_dynamic?(type) do
{tail_position, rest} = decode_type({:uint, 256}, data)
{elements, rest} =
Enum.reduce(types, {[], starting_data}, fn type, {elements, data} ->
if ABI.FunctionSelector.is_dynamic?(type) do
{tail_position, rest} = decode_type({:uint, 256}, data)
{[{:dynamic, type, tail_position}|elements], rest}
else
{el, rest} = decode_type(type, data)
{[el|elements], rest}
end
end)
# Second pass, decode dynamic types
{elements, rest} = Enum.reduce(elements |> Enum.reverse, {[], rest}, fn el, {elements, data} ->
case el do
{:dynamic, type, _tail_position} ->
{[{:dynamic, type, tail_position} | elements], rest}
else
{el, rest} = decode_type(type, data)
{[el|elements], rest}
_ ->
{[el|elements], data}
end
end)
{[el | elements], rest}
end
end)
{elements |> Enum.reverse |> List.to_tuple, rest}
# Second pass, decode dynamic types
{elements, rest} =
Enum.reduce(elements |> Enum.reverse(), {[], rest}, fn el, {elements, data} ->
case el do
{:dynamic, type, _tail_position} ->
{el, rest} = decode_type(type, data)
{[el | elements], rest}
_ ->
{[el | elements], data}
end
end)
{elements |> Enum.reverse() |> List.to_tuple(), rest}
end
defp decode_type(els, _) do
raise "Unsupported decoding type: #{inspect els}"
raise "Unsupported decoding type: #{inspect(els)}"
end
@spec decode_uint(binary(), integer()) :: {integer(), binary()}
@ -259,11 +268,14 @@ defmodule ABI.TypeDecoder do
case padding_direction do
:left ->
<<_padding::binary-size(padding_size_in_bytes), value::binary-size(size_in_bytes), rest::binary()>> = data
<<_padding::binary-size(padding_size_in_bytes), value::binary-size(size_in_bytes),
rest::binary()>> = data
{value, rest}
:right ->
<<value::binary-size(size_in_bytes), _padding::binary-size(padding_size_in_bytes), rest::binary()>> = data
<<value::binary-size(size_in_bytes), _padding::binary-size(padding_size_in_bytes),
rest::binary()>> = data
{value, rest}
end

View File

@ -98,8 +98,7 @@ defmodule ABI.TypeEncoder do
"000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000110000000000000000000000000000000000000000000000000000000000000001"
"""
def encode(data, function_selector) do
encode_method_id(function_selector) <>
encode_raw(data, function_selector.types)
encode_method_id(function_selector) <> encode_raw(data, function_selector.types)
end
@doc """
@ -120,11 +119,13 @@ defmodule ABI.TypeEncoder do
@spec encode_method_id(%ABI.FunctionSelector{}) :: binary()
defp encode_method_id(%ABI.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()
|> ExthCrypto.Hash.Keccak.kec()
kec =
function_selector
|> ABI.FunctionSelector.encode()
|> ExthCrypto.Hash.Keccak.kec()
# Take first four bytes
<<init::binary-size(4), _rest::binary>> = kec
@ -133,65 +134,74 @@ defmodule ABI.TypeEncoder do
init
end
@spec do_encode([ABI.FunctionSelector.type], [any()], [binary()]) :: binary()
@spec do_encode([ABI.FunctionSelector.type()], [any()], [binary()]) :: binary()
defp do_encode([], _, acc), do: :erlang.iolist_to_binary(Enum.reverse(acc))
defp do_encode([type|remaining_types], data, acc) do
defp do_encode([type | remaining_types], data, acc) do
{encoded, remaining_data} = encode_type(type, data)
do_encode(remaining_types, remaining_data, [encoded | acc])
end
@spec encode_type(ABI.FunctionSelector.type, [any()]) :: {binary(), [any()]}
defp encode_type({:uint, size}, [data|rest]) do
@spec encode_type(ABI.FunctionSelector.type(), [any()]) :: {binary(), [any()]}
defp encode_type({:uint, size}, [data | rest]) do
{encode_uint(data, size), rest}
end
defp encode_type(:address, data), do: encode_type({:uint, 160}, data)
defp encode_type(:bool, [data|rest]) do
value = case data do
true -> encode_uint(1, 8)
false -> encode_uint(0, 8)
_ -> raise "Invalid data for bool: #{data}"
end
defp encode_type(:bool, [data | rest]) do
value =
case data do
true -> encode_uint(1, 8)
false -> encode_uint(0, 8)
_ -> raise "Invalid data for bool: #{data}"
end
{value, rest}
end
defp encode_type(:string, [data|rest]) do
defp encode_type(:string, [data | rest]) do
{encode_uint(byte_size(data), 256) <> encode_bytes(data), rest}
end
defp encode_type(:bytes, [data|rest]) do
defp encode_type(:bytes, [data | rest]) do
{encode_uint(byte_size(data), 256) <> encode_bytes(data), rest}
end
defp encode_type({:bytes, size}, [data|rest]) when is_binary(data) and byte_size(data) <= size do
defp encode_type({:bytes, size}, [data | rest])
when is_binary(data) and byte_size(data) <= size do
{encode_bytes(data), rest}
end
defp encode_type({:bytes, size}, [data|_]) when is_binary(data) do
defp encode_type({:bytes, size}, [data | _]) when is_binary(data) do
raise "size mismatch for bytes#{size}: #{inspect(data)}"
end
defp encode_type({:bytes, size}, [data|_]) do
defp encode_type({:bytes, size}, [data | _]) do
raise "wrong datatype for bytes#{size}: #{inspect(data)}"
end
defp encode_type({:tuple, types}, [data|rest]) do
defp encode_type({:tuple, types}, [data | rest]) do
# all head items are 32 bytes in length and there will be exactly
# `count(types)` of them, so the tail starts at `32 * count(types)`.
tail_start = ( types |> Enum.count ) * 32
tail_start = (types |> Enum.count()) * 32
{head, tail, [], _} = Enum.reduce(types, {<<>>, <<>>, data |> Tuple.to_list, tail_start}, fn type, {head, tail, data, tail_position} ->
{el, rest} = encode_type(type, data)
{head, tail, [], _} =
Enum.reduce(types, {<<>>, <<>>, data |> Tuple.to_list(), tail_start}, fn type,
{head, tail, data,
tail_position} ->
{el, rest} = encode_type(type, data)
if ABI.FunctionSelector.is_dynamic?(type) do
# If we're a dynamic type, just encoded the length to head and the element to body
{head <> encode_uint(tail_position, 256), tail <> el, rest, tail_position + byte_size(el)}
else
# If we're a static type, simply encode the el to the head
{head <> el, tail, rest, tail_position}
end
end)
if ABI.FunctionSelector.is_dynamic?(type) do
# If we're a dynamic type, just encoded the length to head and the element to body
{head <> encode_uint(tail_position, 256), tail <> el, rest,
tail_position + byte_size(el)}
else
# If we're a static type, simply encode the el to the head
{head <> el, tail, rest, tail_position}
end
end)
{head <> tail, rest}
end
@ -199,10 +209,10 @@ defmodule ABI.TypeEncoder do
defp encode_type({:array, type, element_count}, [data | rest]) do
repeated_type = Enum.map(1..element_count, fn _ -> type end)
encode_type({:tuple, repeated_type}, [data |> List.to_tuple | rest])
encode_type({:tuple, repeated_type}, [data |> List.to_tuple() | rest])
end
defp encode_type({:array, type}, [data|_rest]=all_data) do
defp encode_type({:array, type}, [data | _rest] = all_data) do
element_count = Enum.count(data)
encoded_uint = encode_uint(element_count, 256)
@ -212,7 +222,7 @@ defmodule ABI.TypeEncoder do
end
defp encode_type(els, _) do
raise "Unsupported encoding type: #{inspect els}"
raise "Unsupported encoding type: #{inspect(els)}"
end
def encode_bytes(bytes) do
@ -222,10 +232,14 @@ defmodule ABI.TypeEncoder do
# Note, we'll accept a binary or an integer here, so long as the
# binary is not longer than our allowed data size
defp encode_uint(data, size_in_bits) when rem(size_in_bits, 8) == 0 do
size_in_bytes = ( size_in_bits / 8 ) |> round
size_in_bytes = (size_in_bits / 8) |> round
bin = maybe_encode_unsigned(data)
if byte_size(bin) > size_in_bytes, do: raise "Data overflow encoding uint, data `#{data}` cannot fit in #{size_in_bytes * 8} bits"
if byte_size(bin) > size_in_bytes,
do:
raise(
"Data overflow encoding uint, data `#{data}` cannot fit in #{size_in_bytes * 8} bits"
)
bin |> pad(size_in_bytes, :left)
end
@ -233,7 +247,7 @@ defmodule ABI.TypeEncoder do
defp pad(bin, size_in_bytes, direction) do
# TODO: Create `left_pad` repo, err, add to `ExthCrypto.Math`
total_size = size_in_bytes + ExthCrypto.Math.mod(32 - size_in_bytes, 32)
padding_size_bits = ( total_size - byte_size(bin) ) * 8
padding_size_bits = (total_size - byte_size(bin)) * 8
padding = <<0::size(padding_size_bits)>>
case direction do
@ -245,5 +259,4 @@ defmodule ABI.TypeEncoder do
@spec maybe_encode_unsigned(binary() | 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

10
mix.exs
View File

@ -2,14 +2,14 @@ defmodule ABI.Mixfile do
use Mix.Project
def project do
[app: :abi,
[app: :ex_abi,
version: "0.1.12",
elixir: "~> 1.4",
elixir: "~> 1.6",
description: "Ethereum's ABI Interface",
package: [
maintainers: ["Geoffrey Hayes", "Mason Fischer"],
licenses: ["MIT"],
links: %{"GitHub" => "https://github.com/exthereum/abi"}
maintainers: ["Ayrat Badykov"],
licenses: ["GPL-3.0"],
links: %{"GitHub" => "https://github.com/poanetwork/ex_abi"}
],
build_embedded: Mix.env == :prod,
start_permanent: Mix.env == :prod,

View File

@ -1,5 +1,4 @@
defmodule ABI.FunctionSelectorTest do
use ExUnit.Case, async: true
doctest ABI.FunctionSelector
end

View File

@ -1,5 +1,4 @@
defmodule ABI.TypeDecoderTest do
use ExUnit.Case, async: true
doctest ABI.TypeDecoder
end

View File

@ -1,5 +1,4 @@
defmodule ABI.TypeEncoderTest do
use ExUnit.Case, async: true
doctest ABI.TypeEncoder
end

View File

@ -1,5 +1,4 @@
defmodule ABITest do
use ExUnit.Case
doctest ABI
end