Pre-release — The API surface may change. Unaudited.
Callcium LogoCallcium

Descriptor

Binary format for describing ABI types in Callcium.

1. Document Control

  • Version: 2.0
  • Status: Normative

2. Purpose, Scope, and Exclusions

This document specifies the Descriptor binary format for Callcium, a policy engine for ABI-encoded data. The descriptor enables deterministic traversal of ABI-encoded data for runtime validation, parsing, and extraction without storing or shipping full ABI JSON.

Exclusions

This document does not define:

  • Validation rule languages or constraints on values (see Callcium Policy Spec).
  • Contract- or application-specific policies.
  • Non-ABI encodings.
  • Implementation strategies, gas optimization techniques, or API design.

3. Terminology and Conformance

  • The key words MUST, MUST NOT, REQUIRED, SHALL, SHALL NOT, SHOULD, SHOULD NOT, RECOMMENDED, MAY in this document are to be interpreted as described in RFC 2119.
  • "Validator" refers to any component that checks a descriptor blob against the invariants of Section 7.
  • "Builder" refers to any component that constructs descriptor blobs from higher-level type definitions.
  • "Reader" refers to any component that traverses ABI-encoded calldata using a descriptor.
  • "ABI word" means a 32-byte slot in ABI encoding.

An implementation is conformant if and only if it meets all MUST/REQUIRED obligations in Sections 4–7.


4. Wire Format

4.1 Top-Level Structure

[version:1][paramCount:1][param_0][param_1]…[param_(paramCount-1)]

version is a single byte identifying the descriptor format version. This specification describes format version VERSION = 0x02. The descriptor format version is independent of the policy format version (see Callcium Policy Spec). Validators MUST reject any descriptor with version != VERSION.

paramCount is a single byte declaring the number of top-level parameters (0–255). The descriptor body MUST contain exactly paramCount parameter descriptors, concatenated back-to-back with no padding. Validators MUST reject descriptors where the parsed count does not equal paramCount, or where trailing bytes remain after parsing the last parameter.

4.2 Elementary Types (Leaf Nodes)

"Elementary" in this specification refers to descriptor leaf nodes: types encoded as a single type code byte with no nested children. This includes bytes (0x70) and string (0x71), which are dynamic in ABI encoding but elementary in the descriptor tree.

  • Encoding: [typeCode].
  • Node length is exactly one byte.
  • ABI head contribution is one word (32 bytes) for all elementary types except bytes and string, which are dynamic.
  • For traversal purposes, elementary types have an implied staticWords: 1 for static elementary types (all except bytes and string), 0 for dynamic elementary types (bytes, string). Composite types carry explicit staticWords in their metadata.

4.3 Composite Types (Common Structure)

All composite types begin with [typeCode][meta].

meta is a big-endian 24-bit (3-byte) field split into two 12-bit components:

  • staticWords (high 12 bits, bits 23–12): number of 32-byte words in the ABI head for this node when it is static. Zero indicates a dynamic node.
  • nodeLength (low 12 bits, bits 11–0): total descriptor node length in bytes for this node.

The following constants define the composite header layout:

  • COMPOSITE_META_SIZE = 3.
  • TUPLE_HEADER_SIZE = 6 (typeCode + meta + fieldCount).
  • ARRAY_HEADER_SIZE = 4 (typeCode + meta). Applies to both static and dynamic arrays.
  • ARRAY_LENGTH_SIZE = 2 (big-endian uint16).

4.4 Static Arrays

Layout: [STATIC_ARRAY][meta][elemDesc][length].

  • length is big-endian uint16.
  • staticWords encodes length * elemStaticWords, or zero if the element type is dynamic. Here and throughout, elemStaticWords refers to the element node's staticWords metadata value (or the implied value 1 for static elementary types, 0 for dynamic elementary types).

4.5 Dynamic Arrays

Layout: [DYNAMIC_ARRAY][meta][elemDesc].

Element descriptors follow immediately after the common header.

4.6 Tuples

Layout: [TUPLE][meta][fieldCount:be16][field_0][field_1]…[field_(fieldCount-1)].

  • fieldCount is big-endian uint16.
  • staticWords is the sum of static words for all static fields; zero if any field is dynamic.

5. Type Codes

5.1 Type Code Ranges

RangeCategoryDescription
0x00Reserved ZeroMUST NOT be used; validators MUST reject descriptors containing this code
0x010x20Unsigned Integers32 variants: uint8uint256
0x210x40Signed Integers32 variants: int8int256
0x410x4FFixed Typesaddress, bool, function (external function pointer)
0x500x6FFixed Bytes32 variants: bytes1bytes32
0x700x7FDynamic Elementarybytes, string
0x800x8FArraysstatic array, dynamic array
0x900x9FTuplesaggregate of fields
0xA00xFFReservedMUST NOT be used; validators MUST reject descriptors containing these codes

5.2 Derivation Formulas

The following formulas are normative and MUST produce the same code points as Appendix A:

  • uint<N>: code = N / 80x010x20.
  • int<N>: code = 0x20 + (N / 8)0x210x40.
  • bytes<N> (N ∈ {1, ..., 32}): code = 0x4F + N0x500x6F.

5.3 Reserved Codes

Code 0x00 is permanently reserved: it is never a valid type code, in this or any future format version. Validators MUST reject it wherever a type code is expected.

Codes not listed in Appendix A within ranges 0x410x4F, 0x700x7F, 0x800x8F, and 0x900x9F are reserved for future use within their respective range. Validators MUST reject them in this format version.


6. Calldata Traversal

Readers MUST maintain a state triple (head, base, descOffset) during path navigation through ABI-encoded calldata.

6.1 State Triple

VariableDescription
headByte offset in calldata for the head slot of the current node.
baseComposite start offset for resolving relative ABI offsets.
descOffsetByte offset into the descriptor for the current node's type.

6.2 Initial State

Given a baseOffset (the byte offset where ABI-encoded parameters begin):

  • head = baseOffset, base = baseOffset, descOffset = HEADER_SIZE (2).
  • The reader resolves the target parameter by iterating through prior parameters, advancing head by each parameter's head contribution and descOffset by each parameter's nodeLength.

The calling layer determines baseOffset. Standard calldata uses baseOffset = 4 (after the 4-byte selector); raw ABI payloads use baseOffset = 0.

6.3 Tuple Descent

To navigate to field childIndex of a tuple at (head, base, descOffset):

  1. Read isDynamic from the tuple's staticWords metadata (0 = dynamic).
  2. Compute the tuple's data region:
    • Dynamic: tupleBase = base + calldataload(head), cursor = tupleBase.
    • Static: tupleBase = base, cursor = head.
  3. Skip fields 0..childIndex-1: for each skipped field, advance cursor by (staticWords == 0 ? 1 : staticWords) * 32 and advance descOffset by the field's nodeLength (1 for elementary types).
  4. Result: (head = cursor, base = tupleBase, descOffset = fieldDescOffset).

6.4 Array Descent

To navigate to element childIndex of an array at (head, base, descOffset):

In both cases below, elementStaticSize = elemStaticWords * 32 — the element's ABI head size in bytes, derived from its descriptor metadata (see Section 4.4 for elemStaticWords).

Dynamic array:

  1. arrayBase = base + calldataload(head).
  2. length = calldataload(arrayBase). The reader MUST check childIndex < length.
  3. headsSection = arrayBase + 32 (skip the length word).
  4. If elements are dynamic: newHead = headsSection + (childIndex * 32), newBase = headsSection.
  5. If elements are static: newHead = headsSection + (childIndex * elementStaticSize), newBase = arrayBase.

Static array:

  1. If elements are dynamic: arrayBase = base + calldataload(head), newHead = arrayBase + (childIndex * 32), newBase = arrayBase.
  2. If elements are static: newHead = head + (childIndex * elementStaticSize), newBase = base (unchanged).

Key invariant: For dynamic elements, base MUST be set to the start of the heads section (where per-element offsets are measured from), not to the array's data start.

6.5 Bounds Checking

Readers MUST verify that ABI offsets read from calldata point within the calldata bounds. An offset that would cause a read beyond calldatasize() MUST cause traversal failure, not silent mis-parsing.

  • Bounds: For every 32-byte read at offset offset, readers MUST check offset + 32 <= calldatasize(). For dynamic-length reads (e.g., bytes/string payload), readers MUST check that the declared length does not extend beyond the calldata boundary.
  • Arithmetic overflow: When computing a resolved offset as base + offset, readers MUST ensure the addition does not overflow.
  • Word alignment: Readers are NOT required to check that ABI offsets are 32-byte aligned. Implementations MAY reject non-aligned offsets as a strictness option but this is not required for conformance.
  • Overlapping regions: Readers are NOT required to detect overlapping data regions. Implementations MAY perform overlap detection as an optional strictness check.

7. Validation Rules

7.1 Well-Formedness

A descriptor is well-formed if it satisfies all of the following invariants. Validators MUST reject a descriptor that is not well-formed before it is used for calldata traversal (Section 6). Where that rejection happens — decoding, storage, or a standalone validation pass — is implementation-defined.

  • DWF-1: The descriptor is at least 2 bytes (version and paramCount are present).
  • DWF-2: version == VERSION.
  • DWF-3: Every type code is assigned in Appendix A; reserved and unassigned codes (Section 5.3) do not appear.
  • DWF-4: Every composite node's metadata lies within the descriptor, its nodeLength is at least the node's header size, and the node's span (nodeLength bytes from its type code) does not extend beyond the descriptor.
  • DWF-5: Every tuple has fieldCount in [1, MAX_TUPLE_FIELDS].
  • DWF-6: Every static array has length in [1, MAX_STATIC_ARRAY_LENGTH].
  • DWF-7: No composite node has a nesting depth greater than MAX_NESTING_DEPTH. The nesting depth of a node is the number of nodes on the path from its top-level parameter node to it, inclusive. Leaf nodes are not subject to the cap.
  • DWF-8: The descriptor body contains exactly paramCount complete parameter nodes with no trailing bytes.

7.2 Validity

A descriptor is valid if it is well-formed and satisfies the following invariants. Builders MUST NOT emit an invalid descriptor. Validators are not required to verify these invariants; calldata traversal (Section 6) is defined only for valid descriptors.

  • DV-1: nodeLength of every composite node equals the actual byte span of that node, and a tuple's fieldCount field descriptors exactly fill its nodeLength.
  • DV-2: staticWords is zero if and only if the node contains a dynamic sub-value; otherwise it equals the node's ABI head size in words (Sections 4.3–4.6).

7.3 Normative Limits

ConstantValueCategoryInvariantDerivation
MAX_NODE_LENGTH4,095 bytesFormat12-bit nodeLength field in composite metadata (0x0FFF).
MAX_STATIC_WORDS4,095 words (~128 KB)Format12-bit staticWords field in composite metadata.
MAX_STATIC_ARRAY_LENGTH4,095 elementsFormatDWF-612-bit field width uniformity with other composite metadata limits.
MAX_TUPLE_FIELDS4,089 fieldsDerivedDWF-5MAX_NODE_LENGTH - TUPLE_HEADER_SIZE (4,095 - 6).
MAX_NESTING_DEPTH64 levelsDesignDWF-7Operational cap on composite nesting; counting rule in Section 7.1.
MAX_PARAMS255Format1-byte paramCount in descriptor header.

Limits without an invariant reference (—) are bounds of the encoding itself: no byte string can exceed them, so there is nothing for a validator to check.

Limit categories:

  • Format: Structural constraint from the binary encoding field width. Cannot change without a format version bump.
  • Derived: Mechanically follows from other limits.
  • Design: Operational cap chosen for implementation safety. Normative and fixed for this format version: all conformant implementations enforce the same value.

8. References

  • ABI Specification (Solidity documentation, applicable to all EVM languages).
  • RFC 2119 — Key words for use in RFCs to Indicate Requirement Levels.
  • Callcium reference implementation (non-normative).
  • Conformance test vector suite: test/vectors/ in the reference implementation repository.

Appendix A. Complete Type Code Assignments

Unsigned Integers (0x010x20)

ABI TypeCodeABI TypeCodeABI TypeCodeABI TypeCode
uint80x01uint720x09uint1360x11uint2000x19
uint160x02uint800x0Auint1440x12uint2080x1A
uint240x03uint880x0Buint1520x13uint2160x1B
uint320x04uint960x0Cuint1600x14uint2240x1C
uint400x05uint1040x0Duint1680x15uint2320x1D
uint480x06uint1120x0Euint1760x16uint2400x1E
uint560x07uint1200x0Fuint1840x17uint2480x1F
uint640x08uint1280x10uint1920x18uint2560x20

Signed Integers (0x210x40)

ABI TypeCodeABI TypeCodeABI TypeCodeABI TypeCode
int80x21int720x29int1360x31int2000x39
int160x22int800x2Aint1440x32int2080x3A
int240x23int880x2Bint1520x33int2160x3B
int320x24int960x2Cint1600x34int2240x3C
int400x25int1040x2Dint1680x35int2320x3D
int480x26int1120x2Eint1760x36int2400x3E
int560x27int1200x2Fint1840x37int2480x3F
int640x28int1280x30int1920x38int2560x40

Fixed Types (0x410x4F)

ABI TypeCode
address0x41
bool0x42
function0x43

function refers to the ABI external function pointer type: a 20-byte address followed by a 4-byte selector, encoded identical to bytes24.

Codes 0x440x4F are reserved and MUST be rejected in this format version.

Fixed Bytes (0x500x6F)

ABI TypeCodeABI TypeCodeABI TypeCodeABI TypeCode
bytes10x50bytes90x58bytes170x60bytes250x68
bytes20x51bytes100x59bytes180x61bytes260x69
bytes30x52bytes110x5Abytes190x62bytes270x6A
bytes40x53bytes120x5Bbytes200x63bytes280x6B
bytes50x54bytes130x5Cbytes210x64bytes290x6C
bytes60x55bytes140x5Dbytes220x65bytes300x6D
bytes70x56bytes150x5Ebytes230x66bytes310x6E
bytes80x57bytes160x5Fbytes240x67bytes320x6F

Dynamic Elementary (0x700x7F)

ABI TypeCode
bytes0x70
string0x71

Codes 0x720x7F are reserved and MUST be rejected in this format version.

Arrays (0x800x8F)

ABI TypeCode
static array0x80
dynamic array0x81

Codes 0x820x8F are reserved and MUST be rejected in this format version.

Tuples (0x900x9F)

ABI TypeCode
tuple0x90

Codes 0x910x9F are reserved and MUST be rejected in this format version.


Appendix B. Changelog

  • v2.0 (2026-08-05): Initial specification.

On this page