ABI (hieroglyph v1.4.0)

Copy Markdown View Source

Documentation for ABI, the function interface language for Solidity. Generally, the ABI describes how to take binary Ethereum and transform it to or from types that Solidity understands.

Agent Discovery

Use ABI.describe/0..2 for progressive API discovery:

ABI.describe()                    # Level 1: all annotated modules
ABI.describe(:abi)                # Level 2: functions in this module
ABI.describe(:abi, :encode)       # Level 3: full contract for encode/2

A static api_manifest.json covering every public function is emitted by mix descripex.manifest --app hieroglyph (a dedicated mix hieroglyph.manifest wrapper ships in 1.2.0 alongside Phase 3 of the agent economy work — see CHANGELOG). Downstream consumers may diff that manifest across hieroglyph version bumps as a contract-stability check.

API Functions

FunctionArityDescriptionParam Kinds
parse_specification1Parses an ABI specification document into a list of ABI.FunctionSelector structs.doc: value
event_signature1Returns the 32-byte topic hash for an event signature.function_signature: value
decode_event4Decodes an event from raw log data and indexed topics.function_signature: value, data: value, topics: value, opts: value
encode_packed2Encodes values using Solidity's non-standard packed mode (abi.encodePacked).signature_or_selector: value, values: value
decode_error2Decodes selector-prefixed revert data against a list of known custom-error definitions.revert_data: value, error_definitions: value
decode_call3Decodes selector-prefixed calldata (4-byte method ID + ABI-encoded args) and verifies the selector matches.signature_or_selector: value, calldata: value, opts: value
decode3Decodes the given data based on the function or tuple signature.function_signature: value, data: value, opts: value
method_id1Returns the 4-byte function selector (method ID) for a function signature.signature: value
encode2Encodes the given data into the function signature or tuple signature.function_signature: value, data: value

Summary

Functions

Return the list of modules registered with this library.

Decodes the given data based on the function or tuple signature.

Decodes selector-prefixed calldata (4-byte method ID followed by ABI-encoded args) and verifies the prefix matches the expected selector.

Decodes selector-prefixed revert data against a list of known custom-error definitions.

Decodes an event, including indexed and non-indexed data.

Return a Level 1 overview of all modules in this library.

Return Level 2 function list for a module (by full atom or short name).

Return Level 3 function detail (or nil if not found).

Encodes the given data into the function signature or tuple signature.

Encodes a list of values using Solidity's non-standard packed mode.

Returns the signature for an event.

Returns the 4-byte function selector (method ID) for a function signature.

Parses the given ABI specification document into an array of ABI.FunctionSelectors.

Functions

__descripex_modules__()

@spec __descripex_modules__() :: [module()]

Return the list of modules registered with this library.

decode(function_signature, data, opts \\ [])

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

Decodes the given data based on the function or tuple signature.

In place of a signature, you can also pass one of the ABI.FunctionSelector structs returned from parse_specification/1.

Options

  • :decode_structs — when true, returns a map keyed by snake_case atoms derived from each parameter's name (instead of the default list). Field-name atoms must already exist in the VM atom table — decode/3 calls String.to_existing_atom/1 and raises ArgumentError when an atom has not been interned. See the README "Pre-interning atoms for decode_structs: true" section for the one-liner migration.

Examples

iex> ABI.decode("baz(uint,address)", "00000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000001" |> Base.decode16!(case: :lower))
[50, <<1::160>>]

iex> ABI.decode("(address[])", "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000" |> Base.decode16!(case: :lower))
[[]]

iex> ABI.decode("(uint256)", "000000000000000000000000000000000000000000000000000000000000000a" |> Base.decode16!(case: :lower))
[10]

iex> ABI.decode("(string)", "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000b457468657220546f6b656e000000000000000000000000000000000000000000" |> Base.decode16!(case: :lower))
["Ether Token"]

iex> ABI.decode("((uint256,uint256),string)", "000000000000000000000000000000000000000000000000000000000000001100000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000b457468657220546f6b656e000000000000000000000000000000000000000000" |> Base.decode16!(case: :lower))
[{0x11, 0x22}, "Ether Token"]

iex> ABI.decode("((uint256,(uint256,uint256)),string)", "0000000000000000000000000000000000000000000000000000000000000011000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000330000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000b457468657220546f6b656e000000000000000000000000000000000000000000" |> Base.decode16!(case: :lower))
[{0x11, {0x22, 0x33}}, "Ether Token"]

iex> File.read!("priv/dog.abi.json")
...> |> Jason.decode!
...> |> ABI.parse_specification
...> |> Enum.find(&(&1.function == "bark")) # bark(address,bool)
...> |> ABI.decode("00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001" |> Base.decode16!(case: :lower))
[<<1::160>>, true]

iex> ABI.decode("(uint256 a,bool b)", "000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000001" |> Base.decode16!(case: :lower), decode_structs: true)
%{a: 10, b: true}

decode_call(signature_or_selector, calldata, opts \\ [])

@spec decode_call(binary() | ABI.FunctionSelector.t(), binary(), keyword()) ::
  {:ok, [any()] | map()} | {:error, decode_call_error()}

Decodes selector-prefixed calldata (4-byte method ID followed by ABI-encoded args) and verifies the prefix matches the expected selector.

Symmetric counterpart to encode/2, which produces selector-prefixed output. Use decode/2 for payload-only data (return values, or calldata that has already been routed by selector).

Returns:

  • {:ok, decoded} — selector matched; decoded is the same shape decode/3 returns
  • {:error, :calldata_too_short} — fewer than 4 bytes provided
  • {:error, :selector_mismatch} — first 4 bytes don't match the expected selector
  • {:error, :no_function_name} — the selector has function: nil, so there's no selector to verify against; use decode/3 with the payload directly

Note

Only the selector check is wrapped in {:error, _}. When the selector matches but the payload is malformed (truncated or wrongly-typed bytes), the underlying decode/3 still raises — same contract as calling decode/3 directly.

Examples

iex> calldata = ABI.encode("transfer(address,uint256)", [<<1::160>>, 100])
iex> ABI.decode_call("transfer(address,uint256)", calldata)
{:ok, [<<1::160>>, 100]}

iex> ABI.decode_call("deposit()", <<0xd0, 0xe3, 0x0d, 0xb0>>)
{:ok, []}

iex> ABI.decode_call("transfer(address,uint256)", <<0xde, 0xad, 0xbe, 0xef>>)
{:error, :selector_mismatch}

iex> ABI.decode_call("transfer(address,uint256)", <<0xa9, 0x05>>)
{:error, :calldata_too_short}

iex> ABI.decode_call(%ABI.FunctionSelector{function: nil, types: []}, <<0::32>>)
{:error, :no_function_name}

decode_error(revert_data, error_definitions)

@spec decode_error(binary(), [binary() | ABI.FunctionSelector.t()]) ::
  {:ok, %{error: String.t() | nil, args: [any()] | map()}}
  | {:error, decode_error_error()}

Decodes selector-prefixed revert data against a list of known custom-error definitions.

Solidity 0.8.4 introduced custom errors; when a contract reverts with revert MyError(arg1, arg2), the revert data is keccak256("MyError(type1,type2)")[0..3] ++ abi.encode(arg1, arg2) — exactly the same shape as a call's selector-prefixed calldata.

This helper mirrors decode_call/3 for that shape: try each candidate error signature against the revert's 4-byte prefix, decode the payload using whichever matches first.

Returns:

  • {:ok, %{error: name, args: decoded}} — the first definition matching the 4-byte selector. name is the error's function name; decoded matches decode/3's shape (a list of args)
  • {:error, :calldata_too_short} — fewer than 4 bytes provided
  • {:error, :no_match} — no definition's selector matched

Note

Only the selector match is wrapped in {:error, _}. When a selector matches but the payload is malformed, the underlying decode/3 still raises — same contract as decode_call/3.

Examples

iex> revert_data = ABI.encode("InsufficientBalance(uint256,uint256)", [10, 100])
iex> ABI.decode_error(revert_data, ["InsufficientBalance(uint256,uint256)"])
{:ok, %{error: "InsufficientBalance", args: [10, 100]}}

iex> revert_data = ABI.encode("Unauthorized(address)", [<<1::160>>])
iex> ABI.decode_error(revert_data, [
...>   "InsufficientBalance(uint256,uint256)",
...>   "Unauthorized(address)"
...> ])
{:ok, %{error: "Unauthorized", args: [<<1::160>>]}}

iex> ABI.decode_error(<<0xde, 0xad, 0xbe, 0xef>>, ["NotFound()"])
{:error, :no_match}

iex> ABI.decode_error(<<0xa9, 0x05>>, ["NotFound()"])
{:error, :calldata_too_short}

decode_event(function_signature, data, topics, opts \\ [])

@spec decode_event(
  binary() | ABI.FunctionSelector.t(),
  binary(),
  [binary()],
  keyword()
) :: {:ok, String.t() | nil, map()} | {:error, ABI.Event.decode_error()}

Decodes an event, including indexed and non-indexed data.

Returns:

  • {:ok, event_name, args_map} on success
  • {:error, {:event_signature_mismatch, %{expected: _, got: _}}}topics[0] did not match keccak256(canonical_signature)
  • {:error, {:topics_length_mismatch, %{got: _, expected: _}}} — topic count did not match the indexed-parameter count (plus the implicit topics[0] slot when check_event_signature: true)
  • {:error, {:malformed_data, message}} — non-indexed payload failed to decode (truncated, wrong types, or otherwise inconsistent with function_selector.types)

Examples

iex> ABI.decode_event(
...>   "Transfer(address indexed from, address indexed to, uint256 amount)",
...>   ~h[0x00000000000000000000000000000000000000000000000000000004a817c800],
...>   [
...>     ~h[0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef],
...>     ~h[0x000000000000000000000000b2b7c1795f19fbc28fda77a95e59edbb8b3709c8],
...>     ~h[0x0000000000000000000000007795126b3ae468f44c901287de98594198ce38ea]
...>   ]
...> )
{:ok,
  "Transfer", %{
    "amount" => 20000000000,
    "from" => ~h[0xb2b7c1795f19fbc28fda77a95e59edbb8b3709c8],
    "to" => ~h[0x7795126b3ae468f44c901287de98594198ce38ea]
}}

iex> ABI.decode_event(
...>   "Transfer(address indexed from, address indexed to, uint256 amount)",
...>   ~h[0x00000000000000000000000000000000000000000000000000000004a817c800],
...>   [
...>     ~h[0x000000000000000000000000b2b7c1795f19fbc28fda77a95e59edbb8b3709c8],
...>     ~h[0x0000000000000000000000007795126b3ae468f44c901287de98594198ce38ea]
...>   ],
...>   check_event_signature: false
...> )
{:ok,
  "Transfer", %{
    "amount" => 20000000000,
    "from" => ~h[0xb2b7c1795f19fbc28fda77a95e59edbb8b3709c8],
    "to" => ~h[0x7795126b3ae468f44c901287de98594198ce38ea]
}}

iex> ABI.decode_event(
...>   %ABI.FunctionSelector{
...>     function: "Transfer",
...>     types: [
...>       %{type: :address, name: "from", indexed: true},
...>       %{type: :address, name: "to", indexed: true},
...>       %{type: {:uint, 256}, name: "amount"},
...>     ]
...>   },
...>   ~h[0x00000000000000000000000000000000000000000000000000000004a817c800],
...>   [
...>     ~h[0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef],
...>     ~h[0x000000000000000000000000b2b7c1795f19fbc28fda77a95e59edbb8b3709c8],
...>     ~h[0x0000000000000000000000007795126b3ae468f44c901287de98594198ce38ea]
...>   ]
...> )
{:ok,
  "Transfer", %{
    "amount" => 20000000000,
    "from" => ~h[0xb2b7c1795f19fbc28fda77a95e59edbb8b3709c8],
    "to" => ~h[0x7795126b3ae468f44c901287de98594198ce38ea]
}}

describe()

@spec describe() :: [map()]

Return a Level 1 overview of all modules in this library.

describe(mod_or_short)

@spec describe(module() | atom()) :: [map()]

Return Level 2 function list for a module (by full atom or short name).

describe(mod_or_short, func_name)

@spec describe(module() | atom(), atom()) :: map() | nil

Return Level 3 function detail (or nil if not found).

encode(function_signature, data)

@spec encode(binary() | ABI.FunctionSelector.t(), [any()]) :: binary()

Encodes the given data into the function signature or tuple signature.

In place of a signature, you can also pass one of the ABI.FunctionSelector structs returned from parse_specification/1.

Examples

iex> ABI.encode("(uint256)", [{10}])
...> |> Base.encode16(case: :lower)
"000000000000000000000000000000000000000000000000000000000000000a"

iex> ABI.encode("baz(uint,address)", [50, <<1::160>>])
...> |> Base.encode16(case: :lower)
"a291add600000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000001"

iex> ABI.encode("price(string)", ["BAT"])
...> |> Base.encode16(case: :lower)
"fe2c6198000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000034241540000000000000000000000000000000000000000000000000000000000"

iex> ABI.encode("baz(uint8)", [9999])
** (RuntimeError) Data overflow encoding uint, data `9999` cannot fit in 8 bits

iex> ABI.encode("(uint,address)", [{50, <<1::160>>}])
...> |> Base.encode16(case: :lower)
"00000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000001"

iex> ABI.encode("(string)", [{"Ether Token"}])
...> |> Base.encode16(case: :lower)
"0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000b457468657220546f6b656e000000000000000000000000000000000000000000"

iex> ABI.encode("((uint256,uint256),string)", [{{0x11, 0x22}, "Ether Token"}])
...> |> Base.encode16(case: :lower)
"000000000000000000000000000000000000000000000000000000000000001100000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000b457468657220546f6b656e000000000000000000000000000000000000000000"

iex> ABI.encode("((uint256,(uint256,uint256)),string)", [{{0x11, {0x22, 0x33}}, "Ether Token"}])
...> |> Base.encode16(case: :lower)
"0000000000000000000000000000000000000000000000000000000000000011000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000330000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000b457468657220546f6b656e000000000000000000000000000000000000000000"

iex> ABI.encode("(string)", [{String.duplicate("1234567890", 10)}])
...> |> Base.encode16(case: :lower)
"000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000643132333435363738393031323334353637383930313233343536373839303132333435363738393031323334353637383930313233343536373839303132333435363738393031323334353637383930313233343536373839303132333435363738393000000000000000000000000000000000000000000000000000000000"

iex> File.read!("priv/dog.abi.json")
...> |> Jason.decode!
...> |> ABI.parse_specification
...> |> Enum.find(&(&1.function == "bark")) # bark(address,bool)
...> |> ABI.encode([<<1::160>>, true])
...> |> Base.encode16(case: :lower)
"b85d0bd200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001"

encode_packed(signature_or_selector, values)

@spec encode_packed(binary() | ABI.FunctionSelector.t(), [any()]) :: binary()

Encodes a list of values using Solidity's non-standard packed mode.

Used primarily for keccak256(abi.encodePacked(...)) Merkle leaves and signature schemes; never used for actual function calls (the spec defines no decoding function — encoding is ambiguous with multiple dynamic args).

Tuple/struct values and nested arrays raise ArgumentError — the spec does not define their packed encoding.

Warning

If both a and b are dynamic types, abi.encodePacked(a, b) is ambiguous: abi.encodePacked("a", "bc") == abi.encodePacked("ab", "c"). Do not feed multiple dynamic args into packed-mode signature schemes without controlling for that collision.

Examples

iex> ABI.encode_packed("foo(int16,bytes1,uint16,string)", [-1, <<0x42>>, 3, "Hello, world!"])
...> |> Base.encode16(case: :lower)
"ffff42000348656c6c6f2c20776f726c6421"

iex> ABI.encode_packed("leaf(address,uint256)", [<<1::160>>, 100])
...> |> Base.encode16(case: :lower)
"00000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000064"

iex> ABI.encode_packed("foo(uint8[])", [[1, 2, 3]])
...> |> Base.encode16(case: :lower)
"000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003"

event_signature(function_signature)

@spec event_signature(binary() | ABI.FunctionSelector.t()) :: binary()

Returns the signature for an event.

Examples

iex> ABI.event_signature("Transfer(address indexed from, address indexed to, uint256 amount)")
...> |> Base.encode16(case: :lower)
"ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"

method_id(signature)

@spec method_id(binary() | ABI.FunctionSelector.t()) :: binary()

Returns the 4-byte function selector (method ID) for a function signature.

The selector is keccak256(canonical_signature) truncated to its first 4 bytes. Returns <<>> for selectors with no function name (anonymous / raw-tuple selectors used for return-value decoding).

Examples

iex> ABI.method_id("transfer(address,uint256)") |> Base.encode16(case: :lower)
"a9059cbb"

iex> ABI.method_id("deposit()") |> Base.encode16(case: :lower)
"d0e30db0"

iex> ABI.method_id(%ABI.FunctionSelector{function: "deposit", types: []}) |> Base.encode16(case: :lower)
"d0e30db0"

iex> ABI.method_id(%ABI.FunctionSelector{function: nil, types: [%{type: {:uint, 256}}]})
""

parse_specification(doc)

@spec parse_specification([map()]) :: [ABI.FunctionSelector.t()]

Parses the given ABI specification document into an array of ABI.FunctionSelectors.

Every entry in the document is parsed — including constructor, fallback, receive, error, and event entries — and returned with its function_type field set accordingly. Filter by that field if you only want plain function entries.

This function can be used in combination with a JSON parser, e.g. Jason, to parse ABI specification JSON files.

Examples

iex> File.read!("priv/dog.abi.json")
...> |> Jason.decode!
...> |> ABI.parse_specification
[%ABI.FunctionSelector{function: "bark", function_type: :function, state_mutability: :nonpayable, returns: [], types: [%{name: "at", type: :address}, %{name: "loudly", type: :bool}]},
 %ABI.FunctionSelector{function: "rollover", function_type: :function, state_mutability: :nonpayable, returns: [%{name: "is_a_good_boy", type: :bool}], types: []}]

iex> [%{
...>   "constant" => true,
...>   "inputs" => [
...>     %{"name" => "at", "type" => "address"},
...>     %{"name" => "loudly", "type" => "bool"}
...>   ],
...>   "name" => "bark",
...>   "outputs" => [],
...>   "payable" => false,
...>   "stateMutability" => "pure",
...>   "type" => "function"
...> }]
...> |> ABI.parse_specification
[
  %ABI.FunctionSelector{function: "bark", function_type: :function, state_mutability: :pure, returns: [], types: [
    %{type: :address, name: "at"},
    %{type: :bool, name: "loudly"}
  ]}
]

iex> [%{
...>   "inputs" => [
...>      %{"name" => "_numProposals", "type" => "uint8"}
...>   ],
...>   "payable" => false,
...>   "stateMutability" => "nonpayable",
...>   "type" => "constructor"
...> }]
...> |> ABI.parse_specification
[%ABI.FunctionSelector{function: nil, function_type: :constructor, state_mutability: :nonpayable, types: [%{name: "_numProposals", type: {:uint, 8}}], returns: nil}]

iex> ABI.decode("(string)", "000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000643132333435363738393031323334353637383930313233343536373839303132333435363738393031323334353637383930313233343536373839303132333435363738393031323334353637383930313233343536373839303132333435363738393000000000000000000000000000000000000000000000000000000000" |> Base.decode16!(case: :lower))
[String.duplicate("1234567890", 10)]

iex> [%{
...>   "payable" => false,
...>   "stateMutability" => "nonpayable",
...>   "type" => "fallback"
...> }]
...> |> ABI.parse_specification
[%ABI.FunctionSelector{function: nil, function_type: :fallback, state_mutability: :nonpayable, returns: nil, types: []}]