ABI: `()` is an empty tuple to the codec and SolCompat, but the JSON ABI rejects it

Open
#1,670 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
5/5
Estimated time
Over a week
Newbie friendliness
38/100
Issue type
Bug
Clarity
Mostly clear
Activity status
Active
Tech stack
rust, solidity

Research direction

Start in crates/hir/src/analysis/ty/abi_ty.rs at semantic_ty_to_abi_desc and reproduce the discrepancy with the provided fe check and fe build example. Compare the unit handling with the codec, SolCompat for (), and empty #[abi] structs; done means one consistent, decided policy is reflected in JSON ABI generation and the repro behaves accordingly.

Written by the indexing model from the issue text.

Description

Fe disagrees with itself about whether () is an external ABI type.

  • The core codec treats () as an empty tuple: HEAD_SIZE = 0, nothing is encoded.
  • Since #1606, impl SolCompat for () spells it "()", so an #[abi] struct with a () field can be an #[event] or #[error] field.
  • JSON ABI generation rejects TyId::unit at every level (semantic_ty_to_abi_desc in crates/hir/src/analysis/ty/abi_ty.rs: "unit type is not a valid external ABI parameter"). The check dates from the first JSON ABI implementation, before () could appear nested.
  • An empty #[abi] struct is the same ABI type and is accepted: SolCompat gives "()", and the JSON ABI emits {"type": "tuple", "components": []}, even as a function input.

Repro: fe check passes, fe build fails with Failed to generate ABI for C: unit type is not a valid external ABI parameter.

use std::abi::sol
use std::evm::Log

#[abi]
pub struct WithUnit {
    pub value: u256,
    pub unit: (),
}

#[event]
struct Emitted {
    w: WithUnit,
}

msg M {
    #[selector = sol("f(uint256)")]
    F { value: u256 } -> u256,
}

pub contract C {
    recv M {
        F { value } -> u256 uses (log: mut Log) {
            log.emit(Emitted { w: WithUnit { value, unit: () } })
            value
        }
    }
}

Options:

  1. Map () to an empty tuple in the JSON ABI and drop the blanket rejection. This matches empty #[abi] structs, the codec and the ABI spec, which allows tuples of n >= 0 types.
  2. Remove impl SolCompat for (). () is then not an external ABI type, and an event or error containing it is rejected at the field.
  3. Reject both () and empty #[abi] structs as external ABI types, as Solidity does, since it disallows empty structs.

Interop caveat for options 1 and 2: Solidity cannot declare an empty tuple or struct, and alloy computes the selector of f(()) but cast abi-encode rejects () values. Empty #[abi] structs already have this caveat.

Found in the review of #1606: https://github.com/argotorg/fe/pull/1606#discussion_r4148997625

Dominant language
Rust
Stars
1.7k
Forks
219
Avg merge
1d 3m
Merged PRs (30d)
162

Getting set up

This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from argotorg/fe

All issues in argotorg/fe

Similar issues

More Rust issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.