Skip to content

Commit b69c8cd

Browse files
committed
Merge branch 'function-signature-parser' into develop
2 parents 903d256 + f85392e commit b69c8cd

File tree

8 files changed

+145
-66
lines changed

8 files changed

+145
-66
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,7 @@ erl_crash.dump
1818

1919
# Also ignore archive artifacts (built via "mix archive.build").
2020
*.ez
21+
22+
# Ignore generated code from yecc and leex
23+
src/*_lexer.erl
24+
src/*_parser.erl

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# 0.1.9
2+
* Add support for parsing ABI specification documents (`.abi.json` files)
3+
* Reimplement function signature parsing using a BNF grammar
4+
* Fix potential stack overflow during encoding/decoding
15
# 0.1.8
26
* Fix ordering of elements in tuples
37
# 0.1.7
@@ -11,4 +15,4 @@
1115
# 0.1.3
1216
* Bugfix for tuples to properly handle head/tail encoding
1317
# 0.1.2
14-
* Add support for tuples, fixed-length and variable length arrays
18+
* Add support for tuples, fixed-length and variable length arrays

lib/abi.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ defmodule ABI do
3636
"b85d0bd200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001"
3737
"""
3838
def encode(function_signature, data) when is_binary(function_signature) do
39-
encode(ABI.FunctionSelector.decode(function_signature), data)
39+
encode(ABI.Parser.parse!(function_signature), data)
4040
end
4141
def encode(%ABI.FunctionSelector{} = function_selector, data) do
4242
ABI.TypeEncoder.encode(data, function_selector)
@@ -67,7 +67,7 @@ defmodule ABI do
6767
[<<1::160>>, true]
6868
"""
6969
def decode(function_signature, data) when is_binary(function_signature) do
70-
decode(ABI.FunctionSelector.decode(function_signature), data)
70+
decode(ABI.Parser.parse!(function_signature), data)
7171
end
7272
def decode(%ABI.FunctionSelector{} = function_selector, data) do
7373
ABI.TypeDecoder.decode(data, function_selector)

lib/abi/function_selector.ex

Lines changed: 19 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@ defmodule ABI.FunctionSelector do
1919
defstruct [:function, :types, :returns]
2020

2121
@doc """
22-
Decodes a function selector to struct. This is a simple version
23-
and we may opt to do format parsing later.
22+
Decodes a function selector to a struct.
2423
2524
## Examples
2625
@@ -82,23 +81,7 @@ defmodule ABI.FunctionSelector do
8281
}
8382
"""
8483
def decode(signature) do
85-
captures = Regex.named_captures(~r/(?<function>[a-zA-Z_$][a-zA-Z_$0-9]*)?\((?<types>(([^,]+),?)*)\)/, signature)
86-
87-
if captures["function"] != "" do
88-
# Encode as a function call
89-
%ABI.FunctionSelector{
90-
function: captures["function"],
91-
types: decode_raw(captures["types"]),
92-
returns: nil
93-
}
94-
else
95-
# Encode as a tuple
96-
%ABI.FunctionSelector{
97-
function: nil,
98-
types: [{:tuple, decode_raw(captures["types"])}],
99-
returns: nil
100-
}
101-
end
84+
ABI.Parser.parse!(signature, as: :selector)
10285
end
10386

10487
@doc """
@@ -108,11 +91,13 @@ defmodule ABI.FunctionSelector do
10891
10992
iex> ABI.FunctionSelector.decode_raw("string,uint256")
11093
[:string, {:uint, 256}]
94+
95+
iex> ABI.FunctionSelector.decode_raw("")
96+
[]
11197
"""
11298
def decode_raw(type_string) do
113-
type_string
114-
|> String.split(",", trim: true)
115-
|> Enum.map(&decode_type/1)
99+
{:tuple, types} = decode_type("(#{type_string})")
100+
types
116101
end
117102

118103
@doc false
@@ -143,50 +128,22 @@ defmodule ABI.FunctionSelector do
143128

144129
defp parse_specification_type(%{"type" => type}), do: decode_type(type)
145130

146-
@doc false
147-
def decode_type(full_type) do
148-
cond do
149-
# Check for array type
150-
captures = Regex.named_captures(~r/(?<type>[a-z0-9]+)\[(?<element_count>\d*)\]/, full_type) ->
151-
type = decode_type(captures["type"])
152-
153-
if captures["element_count"] != "" do
154-
{element_count, ""} = Integer.parse(captures["element_count"])
155-
156-
{:array, type, element_count}
157-
else
158-
{:array, type}
159-
end
160-
# Check for tuples
161-
captures = Regex.named_captures(~r/\((?<types>[a-z0-9\[\]]+,?)+\)/, full_type) ->
162-
types =
163-
String.split(captures["types"], ",", trim: true)
164-
|> Enum.map(fn type -> decode_type(type) end)
165-
166-
{:tuple, types}
167-
true ->
168-
decode_single_type(full_type)
169-
end
170-
end
131+
@doc """
132+
Decodes the given type-string as a single type.
171133
172-
@doc false
173-
def decode_single_type("uint" <> size_str) do
174-
size = case size_str do
175-
"" -> 256 # default
176-
_ ->
177-
{size, ""} = Integer.parse(size_str)
134+
## Examples
178135
179-
size
180-
end
136+
iex> ABI.FunctionSelector.decode_type("uint256")
137+
{:uint, 256}
181138
182-
{:uint, size}
183-
end
139+
iex> ABI.FunctionSelector.decode_type("(bool,address)")
140+
{:tuple, [:bool, :address]}
184141
185-
def decode_single_type("bool"), do: :bool
186-
def decode_single_type("string"), do: :string
187-
def decode_single_type("address"), do: :address
188-
def decode_single_type(els) do
189-
raise "Unsupported type: #{els}"
142+
iex> ABI.FunctionSelector.decode_type("address[][3]")
143+
{:array, {:array, :address}, 3}
144+
"""
145+
def decode_type(single_type) do
146+
ABI.Parser.parse!(single_type, as: :type)
190147
end
191148

192149
@doc """

lib/abi/parser.ex

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
defmodule ABI.Parser do
2+
@moduledoc false
3+
4+
@doc false
5+
def parse!(str, opts \\ []) do
6+
{:ok, tokens, _} = str |> String.to_charlist |> :ethereum_abi_lexer.string
7+
8+
tokens = case opts[:as] do
9+
nil -> tokens
10+
:type -> [{:"expecting type", 1} | tokens]
11+
:selector -> [{:"expecting selector", 1} | tokens]
12+
end
13+
14+
{:ok, ast} = :ethereum_abi_parser.parse(tokens)
15+
16+
case ast do
17+
{:type, type} -> type
18+
{:selector, selector_parts} -> struct!(ABI.FunctionSelector, selector_parts)
19+
end
20+
end
21+
end

mix.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ defmodule ABI.Mixfile do
33

44
def project do
55
[app: :abi,
6-
version: "0.1.8",
6+
version: "0.1.9",
77
elixir: "~> 1.4",
88
description: "Ethereum's ABI Interface",
99
package: [

src/ethereum_abi_lexer.xrl

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
Definitions.
2+
3+
INT = [0-9]+
4+
LETTERS = [a-z_]+
5+
WHITESPACE = [\s\t\n\r]
6+
TYPES = uint|int|address|bool|fixed|uint|ufixed|bytes|function|string
7+
8+
Rules.
9+
10+
{TYPES} : {token, {atom, TokenLine, list_to_atom(TokenChars)}}.
11+
{INT} : {token, {int, TokenLine, list_to_integer(TokenChars)}}.
12+
{LETTERS} : {token, {binary, TokenLine, list_to_binary(TokenChars)}}.
13+
\[ : {token, {'[', TokenLine}}.
14+
\] : {token, {']', TokenLine}}.
15+
\( : {token, {'(', TokenLine}}.
16+
\) : {token, {')', TokenLine}}.
17+
, : {token, {',', TokenLine}}.
18+
-> : {token, {'->', TokenLine}}.
19+
{WHITESPACE}+ : skip_token.
20+
21+
Erlang code.

src/ethereum_abi_parser.yrl

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
Terminals '(' ')' '[' ']' ',' '->' int atom binary 'expecting selector' 'expecting type'.
2+
Nonterminals dispatch selector nontrivial_selector comma_delimited_types type_with_subscripts array_subscripts tuple array_subscript identifier type typespec.
3+
Rootsymbol dispatch.
4+
5+
dispatch -> 'expecting type' type_with_subscripts : {type, '$2'}.
6+
dispatch -> 'expecting selector' selector : {selector, '$2'}.
7+
dispatch -> type_with_subscripts : {selector, #{function => nil, types => ['$1'], returns => nil}}.
8+
dispatch -> nontrivial_selector : {selector, '$1'}.
9+
10+
selector -> typespec : #{function => nil, types => '$1', returns => nil}.
11+
selector -> nontrivial_selector : '$1'.
12+
13+
nontrivial_selector -> typespec '->' type : #{function => nil, types => '$1', returns => '$3'}.
14+
nontrivial_selector -> identifier typespec : #{function => '$1', types => '$2', returns => nil}.
15+
nontrivial_selector -> identifier typespec '->' type : #{function => '$1', types => '$2', returns => '$4'}.
16+
17+
typespec -> '(' ')' : [].
18+
typespec -> '(' comma_delimited_types ')' : '$2'.
19+
20+
tuple -> '(' ')' : {tuple, []}.
21+
tuple -> '(' comma_delimited_types ')' : {tuple, '$2'}.
22+
23+
comma_delimited_types -> type_with_subscripts : ['$1'].
24+
comma_delimited_types -> type_with_subscripts ',' comma_delimited_types : ['$1' | '$3'].
25+
26+
identifier -> atom : atom_to_list(v('$1')).
27+
identifier -> binary : v('$1').
28+
29+
type_with_subscripts -> type : '$1'.
30+
type_with_subscripts -> type array_subscripts : with_subscripts('$1', '$2').
31+
32+
array_subscripts -> array_subscript : ['$1'].
33+
array_subscripts -> array_subscript array_subscripts : ['$1' | '$2'].
34+
35+
array_subscript -> '[' ']' : variable.
36+
array_subscript -> '[' int ']' : v('$2').
37+
38+
type -> atom :
39+
plain_type(v('$1')).
40+
type -> atom int :
41+
juxt_type(v('$1'), v('$2')).
42+
type -> atom int identifier int :
43+
double_juxt_type(v('$1'), v('$4'), v('$2'), v('$3')).
44+
type -> tuple : '$1'.
45+
46+
47+
Erlang code.
48+
49+
v({_Token, _Line, Value}) -> Value.
50+
51+
plain_type(address) -> address;
52+
plain_type(bool) -> bool;
53+
plain_type(function) -> function;
54+
plain_type(string) -> string;
55+
plain_type(bytes) -> bytes;
56+
plain_type(int) -> juxt_type(int, 256);
57+
plain_type(uint) -> juxt_type(uint, 256);
58+
plain_type(fixed) -> double_juxt_type(fixed, "x", 128, 19);
59+
plain_type(ufixed) -> double_juxt_type(ufixed, "x", 128, 19).
60+
61+
with_subscripts(Type, []) -> Type;
62+
with_subscripts(Type, [H | T]) -> with_subscripts(with_subscript(Type, H), T).
63+
64+
with_subscript(Type, variable) -> {array, Type};
65+
with_subscript(Type, N) when is_integer(N), N >= 0 -> {array, Type, N}.
66+
67+
juxt_type(int, M) when M > 0, M =< 256, (M rem 8) =:= 0 -> {int, M};
68+
juxt_type(uint, M) when M > 0, M =< 256, (M rem 8) =:= 0 -> {uint, M};
69+
juxt_type(bytes, M) when M > 0, M =< 32 -> {bytes, M}.
70+
71+
double_juxt_type(fixed, "x", M, N) when M >= 0, M =< 256, (M rem 8) =:= 0, N > 0, N =< 80 -> {fixed, M, N};
72+
double_juxt_type(ufixed, "x", M, N) when M >= 0, M =< 256, (M rem 8) =:= 0, N > 0, N =< 80 -> {ufixed, M, N}.

0 commit comments

Comments
 (0)