Skip to content

Commit

Permalink
Fix type decoder to use lazy stream instead of pre-allocated list (#170)
Browse files Browse the repository at this point in the history
* Fix type decoder to use lazy stream instead of pre-allocated list

* Add regression test for bug fix

* Refactor type decode to use `Stream.duplicate/2`
  • Loading branch information
fedor-ivn committed Jul 4, 2024
1 parent 26621a9 commit 7882bf6
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 4 deletions.
8 changes: 4 additions & 4 deletions lib/abi/type_decoder.ex
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ defmodule ABI.TypeDecoder do
end

defp decode_type({:array, type, size}, data) do
types = List.duplicate(type, size)
types = Stream.duplicate(type, size)
{tuple, bytes} = decode_type({:tuple, types}, data)

{Tuple.to_list(tuple), bytes}
Expand Down Expand Up @@ -298,9 +298,9 @@ defmodule ABI.TypeDecoder do
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)
{size, bytes} = decode_uint(rest_data, 256)

types = List.duplicate(type, count)
types = Stream.duplicate(type, size)

{tuple, _bytes} = decode_type({:tuple, types}, bytes)
{Tuple.to_list(tuple), rest_bytes}
Expand All @@ -310,7 +310,7 @@ defmodule ABI.TypeDecoder do
{offset, rest_bytes} = decode_uint(data, 256)
<<_padding::binary-size(offset), rest_data::binary>> = full_data

types = List.duplicate(type, size)
types = Stream.duplicate(type, size)

{tuple, _} = decode_type({:tuple, types}, rest_data)

Expand Down
44 changes: 44 additions & 0 deletions test/abi/type_decoder_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -713,6 +713,50 @@ defmodule ABI.TypeDecoderTest do
assert [0x123, "Hello, world!"] == TypeDecoder.decode(data, types)
assert data == data |> TypeDecoder.decode(types) |> TypeEncoder.encode(types)
end

test "handles huge number of list items while ABI decoding" do
selector = %FunctionSelector{
function: "swapExactTokensForTokens",
method_id: <<42, 68, 63, 174>>,
type: :function,
inputs_indexed: nil,
state_mutability: :non_payable,
input_names: ["amountIn", "amountOutMin", "path", "to", "deadline"],
types: [
{:uint, 256},
{:uint, 256},
{:tuple, [array: {:uint, 256}, array: {:uint, 8}, array: :address]},
:address,
{:uint, 256}
],
returns: [uint: 256],
return_names: ["amountOut"]
}

data =
<<42, 68, 63, 174, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 110, 71, 248, 212, 138, 1, 180, 77,
243, 255, 243, 93, 37, 138, 16, 163, 174, 220, 17, 76, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 212, 153, 236, 108, 99, 56, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 143, 154, 114, 216, 245, 100, 121, 144,
58, 227, 134, 132, 158, 41, 13, 73, 56, 158, 65, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 233, 25, 9, 44, 199, 203, 210, 9, 122, 227, 21, 143, 114, 218, 72, 74, 200, 19, 183,
75, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 137, 134, 203, 129, 146, 34, 242, 134, 159, 7,
202, 92, 60, 237, 11, 130, 38, 218, 44, 74, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 164,
168, 239, 101, 138, 224, 219, 204, 165, 88, 224, 178, 219, 217, 229, 25, 37, 24, 13, 50,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 245, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 246, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 245>>

assert_raise MatchError, fn ->
TypeDecoder.decode(data, selector)
end
end
end

describe "with examples from solidity docs" do
Expand Down

0 comments on commit 7882bf6

Please sign in to comment.