Compare commits

...

2 Commits

Author SHA1 Message Date
Fedor Ivanov 67eb2b9d5d
Fix parsing when function ABI is missing the `outputs` field (#163)
* Fix parsing when function ABI is missing the "outputs" field

* Refactor docstrings and comments
2024-03-12 14:26:11 +02:00
Fedor Ivanov e3dc19b814
Fix parsing tuples (#164)
* Fix parsing tuples

* Add regression test for `tuple` parsing

* Apply suggestions
2024-03-12 14:22:03 +02:00
2 changed files with 109 additions and 22 deletions

View File

@ -180,7 +180,31 @@ defmodule ABI.FunctionSelector do
"name" => function_name,
"inputs" => named_inputs,
"outputs" => named_outputs
} <- item,
} <-
item
# Workaround for ABIs that are missing the "outputs" field.
#
# For instance, consider this valid ABI:
#
# ```jsonc
# // ...
# {
# "type": "function",
# "stateMutability": "view",
# "name": "assumeLastTokenIdMatches",
# "inputs": [
# {
# "type": "uint256",
# "name": "lastTokenId",
# "internalType": "uint256"
# }
# ]
# },
# // ...
# ```
# If the "outputs" field is missing, we should assume it's an empty
# list to continue parsing the ABI.
|> Map.put_new("outputs", []),
true <- simple_types?(named_inputs, item),
true <- simple_types?(named_outputs, item) do
input_types = Enum.map(named_inputs, &parse_specification_type/1)
@ -311,34 +335,28 @@ defmodule ABI.FunctionSelector do
end
end
@doc false
def parse_specification_type(%{"type" => "tuple", "components" => components}) do
sub_types = for component <- components, do: parse_specification_type(component)
defp replace_tuple({:array, inner}, sub_types) do
{:array, replace_tuple(inner, sub_types)}
end
defp replace_tuple({:array, inner, size}, sub_types) do
{:array, replace_tuple(inner, sub_types), size}
end
defp replace_tuple(:tuple, sub_types) do
{:tuple, sub_types}
end
def parse_specification_type(%{"type" => "tuple[]", "components" => components}) do
sub_types = for component <- components, do: parse_specification_type(component)
{:array, {:tuple, sub_types}}
defp replace_tuple(other, _) do
other
end
def parse_specification_type(%{"type" => "tuple[][]", "components" => components}) do
sub_types = for component <- components, do: parse_specification_type(component)
{:array, {:array, {:tuple, sub_types}}}
end
def parse_specification_type(%{
"type" => "tuple[" <> tail,
"components" => components
}) do
def parse_specification_type(%{"type" => "tuple" <> _ = type, "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}
type
|> decode_type()
|> replace_tuple(sub_types)
end
def parse_specification_type(%{"type" => type}), do: decode_type(type)

View File

@ -111,6 +111,43 @@ defmodule ABI.FunctionSelectorTest do
}
] == ABI.parse_specification([abi])
end
@doc """
Regression test to verify the correct parsing of ABIs that lack the
"outputs" field.
"""
test "with the missing outputs field" do
abi = [
%{
"type" => "function",
"stateMutability" => "view",
"name" => "assumeLastTokenIdMatches",
"inputs" => [
%{
"type" => "uint256",
"name" => "lastTokenId",
"internalType" => "uint256"
}
]
}
]
expected = [
%FunctionSelector{
type: :function,
function: "assumeLastTokenIdMatches",
input_names: ["lastTokenId"],
types: [uint: 256],
returns: [],
return_names: [],
method_id: <<231, 40, 120, 180>>,
inputs_indexed: nil,
state_mutability: :view
}
]
assert ABI.parse_specification(abi) == expected
end
end
describe "parse_specification_item/1" do
@ -345,6 +382,38 @@ defmodule ABI.FunctionSelectorTest do
assert expected_type == selector.types
end
test "parses fixed 2D array of tuples" do
function = %{
"inputs" => [],
"name" => "createTupleArray",
"outputs" => [
%{
"components" => [
%{
"internalType" => "uint256",
"name" => "element1",
"type" => "uint256"
},
%{"internalType" => "bool", "name" => "element2", "type" => "bool"}
],
"internalType" => "struct StorageB.MyTuple[2][]",
"name" => "",
"type" => "tuple[2][]"
}
],
"stateMutability" => "pure",
"type" => "function"
}
expected = [
array: {:array, {:tuple, [{:uint, 256}, :bool]}, 2}
]
selector = FunctionSelector.parse_specification_item(function)
assert expected == selector.returns
end
test "with stateMutability set" do
~w(pure view nonpayable payable)
|> Enum.zip(~w(pure view non_payable payable)a)