diff --git a/lib/abi/function_selector.ex b/lib/abi/function_selector.ex index 98247a3..f89a370 100644 --- a/lib/abi/function_selector.ex +++ b/lib/abi/function_selector.ex @@ -5,6 +5,7 @@ defmodule ABI.FunctionSelector do """ require Integer + require Logger @type type :: {:uint, integer()} @@ -52,6 +53,19 @@ defmodule ABI.FunctionSelector do returns: [] ] + @simple_types [ + "uint", + "int", + "address", + "bool", + "fixed", + "uint", + "ufixed", + "bytes", + "string", + "tuple" + ] + @doc """ Decodes a function selector to a struct. @@ -150,64 +164,72 @@ defmodule ABI.FunctionSelector do @doc false def parse_specification_item(%{"type" => "function"} = item) do - %{ - "name" => function_name, - "inputs" => named_inputs, - "outputs" => named_outputs - } = item - - input_types = Enum.map(named_inputs, &parse_specification_type/1) - input_names = Enum.map(named_inputs, &Map.get(&1, "name")) - - output_types = Enum.map(named_outputs, &parse_specification_type/1) - - selector = %ABI.FunctionSelector{ - function: function_name, - types: input_types, - returns: output_types, - input_names: input_names, - type: :function - } + with %{ + "name" => function_name, + "inputs" => named_inputs, + "outputs" => named_outputs + } <- item, + true <- simple_types?(named_inputs, item), + true <- simple_types?(named_outputs, item) do + input_types = Enum.map(named_inputs, &parse_specification_type/1) + input_names = Enum.map(named_inputs, &Map.get(&1, "name")) + + output_types = Enum.map(named_outputs, &parse_specification_type/1) + + selector = %ABI.FunctionSelector{ + function: function_name, + types: input_types, + returns: output_types, + input_names: input_names, + type: :function + } - add_method_id(selector) + add_method_id(selector) + else + _ -> nil + end end def parse_specification_item(%{"type" => "constructor"} = item) do - %{ - "inputs" => named_inputs - } = item - - input_types = Enum.map(named_inputs, &parse_specification_type/1) - input_names = Enum.map(named_inputs, &Map.get(&1, "name")) - - selector = %ABI.FunctionSelector{ - types: input_types, - input_names: input_names, - type: :constructor - } + with %{"inputs" => named_inputs} <- item, + true <- simple_types?(named_inputs, item) do + input_types = Enum.map(named_inputs, &parse_specification_type/1) + input_names = Enum.map(named_inputs, &Map.get(&1, "name")) + + selector = %ABI.FunctionSelector{ + types: input_types, + input_names: input_names, + type: :constructor + } - add_method_id(selector) + add_method_id(selector) + else + _ -> nil + end end def parse_specification_item(%{"type" => "event"} = item) do - %{ - "name" => event_name, - "inputs" => named_inputs - } = item - - input_types = Enum.map(named_inputs, &parse_specification_type/1) - input_names = Enum.map(named_inputs, &Map.get(&1, "name")) - inputs_indexed = Enum.map(named_inputs, &Map.get(&1, "indexed")) - - selector = %ABI.FunctionSelector{ - function: event_name, - types: input_types, - input_names: input_names, - inputs_indexed: inputs_indexed, - type: :event - } + with %{ + "name" => event_name, + "inputs" => named_inputs + } <- item, + true <- simple_types?(named_inputs, item) do + input_types = Enum.map(named_inputs, &parse_specification_type/1) + input_names = Enum.map(named_inputs, &Map.get(&1, "name")) + inputs_indexed = Enum.map(named_inputs, &Map.get(&1, "indexed")) + + selector = %ABI.FunctionSelector{ + function: event_name, + types: input_types, + input_names: input_names, + inputs_indexed: inputs_indexed, + type: :event + } - add_method_id(selector) + add_method_id(selector) + else + _ -> nil + end end def parse_specification_item(%{"type" => "fallback"}) do @@ -223,6 +245,37 @@ defmodule ABI.FunctionSelector do def parse_specification_item(_), do: nil + @spec simple_types?([map()], map()) :: boolean() + def simple_types?([], _item), do: true + + def simple_types?([%{"type" => tuple_type, "components" => current_types} | types], item) + when tuple_type in ["tuple", "tuple[]"] do + case simple_types?(current_types, item) do + true -> + simple_types?(types, item) + + false -> + Logger.warn("Can not parse #{inspect(item)} because it contains complex types") + false + end + end + + def simple_types?([%{"type" => type} | types], item) do + simple_type? = + Enum.any?(@simple_types, fn simple_type -> + String.contains?(type, simple_type) + end) + + case simple_type? do + true -> + simple_types?(types, item) + + false -> + Logger.warn("Can not parse #{inspect(item)} because it contains complex types") + false + end + end + @doc false def parse_specification_type(%{"type" => "tuple", "components" => components}) do sub_types = for component <- components, do: parse_specification_type(component) @@ -234,6 +287,20 @@ defmodule ABI.FunctionSelector do {:array, {:tuple, sub_types}} end + def parse_specification_type(%{ + "type" => "tuple[" <> tail, + "components" => components + }) do + sub_types = for component <- components, do: parse_specification_type(component) + + size = + tail + |> String.replace("]", "") + |> String.to_integer() + + {:array, {:tuple, sub_types}, size} + end + def parse_specification_type(%{"type" => type}), do: decode_type(type) @doc """ diff --git a/test/abi/function_selector_test.exs b/test/abi/function_selector_test.exs index 1df3c87..3066d87 100644 --- a/test/abi/function_selector_test.exs +++ b/test/abi/function_selector_test.exs @@ -1,5 +1,8 @@ defmodule ABI.FunctionSelectorTest do use ExUnit.Case, async: true + + import ExUnit.CaptureLog + doctest ABI.FunctionSelector alias ABI.FunctionSelector @@ -143,5 +146,244 @@ defmodule ABI.FunctionSelectorTest do assert expected_type == selector.returns end + + test "parses fixed array of tuples" do + function = %{ + "constant" => false, + "inputs" => [ + %{"internalType" => "uint160", "name" => "exitId", "type" => "uint160"}, + %{ + "components" => [ + %{"internalType" => "bool", "name" => "isCanonical", "type" => "bool"}, + %{ + "internalType" => "uint64", + "name" => "exitStartTimestamp", + "type" => "uint64" + }, + %{"internalType" => "uint256", "name" => "exitMap", "type" => "uint256"}, + %{ + "internalType" => "uint256", + "name" => "position", + "type" => "uint256" + }, + %{ + "components" => [ + %{ + "internalType" => "bytes32", + "name" => "outputId", + "type" => "bytes32" + }, + %{ + "internalType" => "address payable", + "name" => "exitTarget", + "type" => "address" + }, + %{ + "internalType" => "address", + "name" => "token", + "type" => "address" + }, + %{ + "internalType" => "uint256", + "name" => "amount", + "type" => "uint256" + }, + %{ + "internalType" => "uint256", + "name" => "piggybackBondSize", + "type" => "uint256" + } + ], + "internalType" => "struct PaymentExitDataModel.WithdrawData[4]", + "name" => "inputs", + "type" => "tuple[4]" + }, + %{ + "components" => [ + %{ + "internalType" => "bytes32", + "name" => "outputId", + "type" => "bytes32" + }, + %{ + "internalType" => "address payable", + "name" => "exitTarget", + "type" => "address" + }, + %{ + "internalType" => "address", + "name" => "token", + "type" => "address" + }, + %{ + "internalType" => "uint256", + "name" => "amount", + "type" => "uint256" + }, + %{ + "internalType" => "uint256", + "name" => "piggybackBondSize", + "type" => "uint256" + } + ], + "internalType" => "struct PaymentExitDataModel.WithdrawData[4]", + "name" => "outputs", + "type" => "tuple[4]" + }, + %{ + "internalType" => "address payable", + "name" => "bondOwner", + "type" => "address" + }, + %{ + "internalType" => "uint256", + "name" => "bondSize", + "type" => "uint256" + }, + %{ + "internalType" => "uint256", + "name" => "oldestCompetitorPosition", + "type" => "uint256" + } + ], + "internalType" => "struct PaymentExitDataModel.InFlightExit", + "name" => "exit", + "type" => "tuple" + } + ], + "name" => "setInFlightExit", + "outputs" => [], + "payable" => false, + "stateMutability" => "nonpayable", + "type" => "function" + } + + expected_type = [ + {:uint, 160}, + {:tuple, + [ + :bool, + {:uint, 64}, + {:uint, 256}, + {:uint, 256}, + {:array, {:tuple, [{:bytes, 32}, :address, :address, {:uint, 256}, {:uint, 256}]}, 4}, + {:array, {:tuple, [{:bytes, 32}, :address, :address, {:uint, 256}, {:uint, 256}]}, 4}, + :address, + {:uint, 256}, + {:uint, 256} + ]} + ] + + selector = FunctionSelector.parse_specification_item(function) + + assert expected_type == selector.types + end + end + + describe "simple_types?/1" do + test "verifies simple types" do + types = [ + %{ + "internalType" => "uint256", + "name" => "erc20VaultId", + "type" => "uint256" + }, + %{ + "internalType" => "uint256", + "name" => "supportedTxType", + "type" => "uint256" + } + ] + + assert FunctionSelector.simple_types?(types, %{}) + end + + test "verifies tuple type" do + types = [ + %{ + "components" => [ + %{ + "internalType" => "uint256", + "name" => "minExitPeriod", + "type" => "uint256" + } + ], + "internalType" => "struct ExitableTimestamp.Calculator", + "name" => "exitableTimestampCalculator", + "type" => "tuple" + } + ] + + assert FunctionSelector.simple_types?(types, %{}) + end + + test "invalidates complex type" do + types = [ + %{ + "internalType" => "contract PlasmaFramework", + "name" => "framework", + "type" => "PlasmaFramework" + } + ] + + assert capture_log(fn -> + refute FunctionSelector.simple_types?(types, %{}) + end) =~ + "Can not parse %{} because it contains complex types" + end + + test "invalidates comples tuple type" do + types = [ + %{ + "components" => [ + %{ + "components" => [ + %{ + "internalType" => "uint256", + "name" => "minExitPeriod", + "type" => "uint256" + } + ], + "internalType" => "struct ExitableTimestamp.Calculator", + "name" => "exitableTimestampCalculator", + "type" => "tuple" + }, + %{ + "internalType" => "uint256", + "name" => "ethVaultId", + "type" => "uint256" + }, + %{ + "internalType" => "uint256", + "name" => "erc20VaultId", + "type" => "uint256" + }, + %{ + "internalType" => "uint256", + "name" => "supportedTxType", + "type" => "uint256" + }, + %{ + "internalType" => "contract IExitProcessor", + "name" => "exitProcessor", + "type" => "IExitProcessor" + }, + %{ + "internalType" => "contract PlasmaFramework", + "name" => "framework", + "type" => "PlasmaFramework" + } + ], + "internalType" => "struct PaymentStartStandardExit.Controller", + "name" => "", + "type" => "tuple" + } + ] + + assert capture_log(fn -> + refute FunctionSelector.simple_types?(types, %{}) + end) =~ + "Can not parse %{} because it contains complex types" + end end end