ABI.TypeDecoder (hieroglyph v1.4.0)

Copy Markdown View Source

ABI.TypeDecoder is responsible for decoding types to the format expected by Solidity. We generally take a function selector and binary data and decode that into the original arguments according to the specification.

API Functions

FunctionArityDescriptionParam Kinds
decode_bytes3Read size_in_bytes of content from a 32-byte-aligned ABI word, skipping padding on the matching side. Used to extract address, uint/int, bytes<M>, and string payloads from their slots.data: value, size_in_bytes: value, padding_direction: value
tuple_value3Combine a list of ABI argument types with decoded element values, returning either a tuple or, when decode_structs is enabled and every type carries a non-empty :name, a map keyed by snake_case atom field names.types: value, elements: value, decode_structs: value
decode_raw3Decode an ABI-encoded payload directly against an explicit type list, without consulting a FunctionSelector.encoded_data: value, types: value, opts: value
decode3Decode an ABI-encoded payload into a list of values, using a FunctionSelector to drive type interpretation.encoded_data: value, function_selector: value, opts: value

Summary

Functions

Decodes the given data based on the function selector.

Reads size_in_bytes of content out of data, skipping the 32-byte-slot padding on whichever side matches padding_direction (:left for left-padded types like address and uint/int, :right for right-padded types like bytes<M> and string). Returns {value, rest}.

Similar to ABI.TypeDecoder.decode/2 except accepts a list of types instead of a function selector.

Combines a list of ABI argument types with a list of decoded element values into either a tuple or (when decode_structs is true and every type carries a non-empty :name) a map keyed by the existing snake_case atom for each field name.

Functions

decode(encoded_data, function_selector, opts \\ [])

@spec decode(binary(), ABI.FunctionSelector.t(), keyword()) :: [any()]

Decodes the given data based on the function selector.

Note, we don't currently try to guess the function name?

Examples

iex> "00000000000000000000000000000000000000000000000000000000000000450000000000000000000000000000000000000000000000000000000000000001"
...> |> Base.decode16!(case: :lower)
...> |> ABI.TypeDecoder.decode(
...>      %ABI.FunctionSelector{
...>        function: "baz",
...>        types: [
...>          %{type: {:uint, 32}},
...>          %{type: :bool}
...>        ],
...>        returns: :bool
...>      }
...>    )
[69, true]

iex> "000000000000000000000000000000000000000000000000000000000000000b68656c6c6f20776f726c64000000000000000000000000000000000000000000"
...> |> Base.decode16!(case: :lower)
...> |> ABI.TypeDecoder.decode(
...>      %ABI.FunctionSelector{
...>        function: nil,
...>        types: [
...>          %{type: :string}
...>        ]
...>      }
...>    )
["hello world"]

iex> "00000000000000000000000000000000000000000000000000000000000000110000000000000000000000000000000000000000000000000000000000000001"
...> |> Base.decode16!(case: :lower)
...> |> ABI.TypeDecoder.decode(
...>      %ABI.FunctionSelector{
...>        function: nil,
...>        types: [
...>          %{type: {:tuple, [%{type: {:uint, 32}, name: "a"}, %{type: :bool, name: "b"}]}}
...>        ]
...>      }
...>    )
[{17, true}]

iex> "00000000000000000000000000000000000000000000000000000000000000110000000000000000000000000000000000000000000000000000000000000001"
...> |> Base.decode16!(case: :lower)
...> |> ABI.TypeDecoder.decode(
...>      %ABI.FunctionSelector{
...>        function: nil,
...>        types: [
...>          %{type: {:tuple, [%{type: {:uint, 32}, name: "a"}, %{type: :bool, name: "b"}]}}
...>        ]
...>      },
...>      decode_structs: true
...>    )
[%{a: 17, b: true}]

iex> "00000000000000000000000000000000000000000000000000000000000000110000000000000000000000000000000000000000000000000000000000000001"
...> |> Base.decode16!(case: :lower)
...> |> ABI.TypeDecoder.decode(
...>      %ABI.FunctionSelector{
...>        function: nil,
...>        types: [
...>          %{type: {:tuple, [%{type: {:uint, 32}}, %{type: :bool}]}}
...>        ]
...>      }
...>    )
[{17, true}]

iex> "00000000000000000000000000000000000000000000000000000000000000110000000000000000000000000000000000000000000000000000000000000001"
...> |> Base.decode16!(case: :lower)
...> |> ABI.TypeDecoder.decode(
...>      %ABI.FunctionSelector{
...>        function: nil,
...>        types: [
...>          %{type: {:array, {:uint, 32}, 2}}
...>        ]
...>      }
...>    )
[[17, 1]]

iex> "000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000110000000000000000000000000000000000000000000000000000000000000001"
...> |> Base.decode16!(case: :lower)
...> |> ABI.TypeDecoder.decode(
...>      %ABI.FunctionSelector{
...>        function: nil,
...>        types: [
...>          %{type: {:array, {:uint, 32}}}
...>        ]
...>      }
...>    )
[[17, 1]]

iex> "0000000000000000000000000000000000000000000000000000000000000011000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000011020000000000000000000000000000000000000000000000000000000000000"
...> |> Base.decode16!(case: :lower)
...> |> ABI.TypeDecoder.decode(
...>      %ABI.FunctionSelector{
...>        function: nil,
...>        types: [
...>          %{type: {:array, {:uint, 32}, 2}},
...>          %{type: :bool},
...>          %{type: {:bytes, 2}}
...>        ]
...>      }
...>    )
[[17, 1], true, <<16, 32>>]

iex> "000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000007617765736f6d6500000000000000000000000000000000000000000000000000"
...> |> Base.decode16!(case: :lower)
...> |> ABI.TypeDecoder.decode(
...>      %ABI.FunctionSelector{
...>        function: nil,
...>        types: [
...>          %{type: {:tuple, [%{type: :string}, %{type: :bool}]}}
...>        ]
...>      }
...>    )
[{"awesome", true}]

iex> "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000"
...> |> Base.decode16!(case: :lower)
...> |> ABI.TypeDecoder.decode(
...>      %ABI.FunctionSelector{
...>        function: nil,
...>        types: [
...>          %{type: {:tuple, [%{type: {:array, :address}}]}}
...>        ]
...>      }
...>    )
[{[]}]

iex> "00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000c556e617574686f72697a656400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000204a2bf2ff0a4eaf1890c8d8679eaa446fb852c4000000000000000000000000861d9af488d5fa485bb08ab6912fff4f7450849a"
...> |> Base.decode16!(case: :lower)
...> |> ABI.TypeDecoder.decode(
...>      %ABI.FunctionSelector{
...>        function: nil,
...>        types: [%{type: {:tuple,[
...>          %{type: :string},
...>          %{type: {:array, {:uint, 256}}}
...>        ]}}]
...>      }
...>    )
[{
  "Unauthorized",
  [
    184341788326688649239867304918349890235378717380,
    765664983403968947098136133435535343021479462042,
  ]
}]

iex> "000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000034241540000000000000000000000000000000000000000000000000000000000"
...> |> Base.decode16!(case: :lower)
...> |> ABI.TypeDecoder.decode(
...>      %ABI.FunctionSelector{
...>        function: "price",
...>        types: [
...>          %{type: :string}
...>        ],
...>        returns: {:uint, 256}
...>      }
...>    )
["BAT"]

decode_bytes(data, size_in_bytes, padding_direction)

@spec decode_bytes(binary(), integer(), atom()) :: {binary(), binary()}

Reads size_in_bytes of content out of data, skipping the 32-byte-slot padding on whichever side matches padding_direction (:left for left-padded types like address and uint/int, :right for right-padded types like bytes<M> and string). Returns {value, rest}.

decode_raw(encoded_data, types, opts \\ [])

@spec decode_raw(binary(), [ABI.FunctionSelector.argument_type()], keyword()) :: [
  any()
]

Similar to ABI.TypeDecoder.decode/2 except accepts a list of types instead of a function selector.

Examples

iex> "000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000007617765736f6d6500000000000000000000000000000000000000000000000000"
...> |> Base.decode16!(case: :lower)
...> |> ABI.TypeDecoder.decode_raw([%{type: {:tuple, [%{type: :string}, %{type: :bool}]}}])
[{"awesome", true}]

tuple_value(types, elements, decode_structs)

@spec tuple_value([ABI.FunctionSelector.argument_type()], [any()], boolean()) ::
  map() | tuple()

Combines a list of ABI argument types with a list of decoded element values into either a tuple or (when decode_structs is true and every type carries a non-empty :name) a map keyed by the existing snake_case atom for each field name.

Field-name atoms must already exist in the VM atom table — decode_structs: true calls String.to_existing_atom/1 on Macro.underscore(name) and raises ArgumentError if the atom has not been interned. This bounds atom creation to the set of field names the caller has explicitly referenced in their code, closing a DoS surface for consumers that ingest ABIs from arbitrary sources.

Used internally by decode_type({:tuple, types}, ...) to render the second-pass result; exposed because event-log decoding in ABI.Event reuses the same shape.