Записи о решениях · ADR-023
ADR-023: Several values in one transfer's data, each under its own key
AREX CORE §4 passes the data of an ERC 1155 transfer to every hook, unchanged. Two reference hooks read it, and each gives the same 32 bytes its own meaning: DeliveryHook reads uint256 deliveryId (a transfer settles the
- Идентификатор документа
- ADR-023
- Дата
- 2026-10-27
- Источник
- docs/adr/ADR-023.md
When more than one rule reads the data of a transfer, the data is a tagged envelope of (key, value) pairs, each
rule reads only its own key, and 32-byte data keeps its single meaning for rules written before the envelope.
| Field | Value |
|---|---|
| ID | ADR-023 |
| Subject area | Extensibility, hook data |
| Status | Proposed (2026-10-27). Reference hooks available in arex-contracts 2.0.0-alpha.7; informative, adds no requirement to AREX-CORE.md |
| Date | 2026-10-27 |
| Owners | A-REX Contributors; requested by the A-REX Platform (lot-rules audit, item K3) |
| Related requirements | AREX-CORE §4 (data passed to hooks), §5, ADR-004, ADR-005, ADR-019 |
| Supersedes / superseded by | — |
Context
AREX-CORE §4 passes the data of an ERC-1155 transfer to every hook, unchanged. Two reference hooks read it, and each
gives the same 32 bytes its own meaning: DeliveryHook reads uint256 deliveryId (a transfer settles the delivery
it names), TraceabilityHook reads bytes32 ref (the document of the movement, such as the hash of an e-CMR or a bill
of lading). With both on one token, a transfer can carry only one of them: settling a tracked delivery writes its
number as the trace entry's document, and a document reference whose value happens to equal a delivery's number could
settle that delivery. The A-REX Platform worked around it outside the ledger (one meaning per transfer, read back in
words; a separate trace entry for the document), but every implementer combining rules meets the same problem, and any
third rule that reads data adds a third meaning.
Issue
How can several rules read values from the data of one transfer without giving the same bytes two meanings, and
without breaking rules already deployed that read 32 bytes?
Assumptions
- Rules are independent contracts that see the same
data(ADR-004); none can strip or rewrite it for the next. - Values a rule reads from
dataare small identifiers or hashes (32 bytes each); documents themselves stay off-chain. - Deployed hooks cannot be upgraded (reference hooks are not proxies); a new format reaches them only through new instances.
Constraints
- AREX-CORE §4:
datais passed unchanged; the token does not interpret it. - ERC-1155 also passes
datato a contract recipient's acceptance callback. - Reading must be cheap and bounded (hooks run on every transfer) and must never revert on malformed data.
Alternatives considered
| Alternative | For | Against | Outcome |
|---|---|---|---|
| One meaning per transfer, chosen off-chain (status quo) | no contract change | the ledger cannot tell which meaning was meant; values collide; each implementer re-solves it | Rejected as a long-term answer (kept for older instances) |
A registry in the token mapping data slices to hooks | explicit | changes the core token and the interfaces; the token would interpret data | Rejected |
| ABI-encoded struct with one field per known rule | simple to decode | closed set: a new rule needs a new struct; empty fields cost bytes | Rejected |
A tagged envelope of (bytes32 key, bytes32 value) pairs, keys namespaced like codes | open set, like codes (ADR-005, ADR-006); bounded; 32-byte data stays distinct | one more convention to document | Proposed |
Decision (proposed)
The data a hook reads is one of:
- 32 bytes: one value, read by each rule with the meaning it had before this record (legacy, unchanged);
- a tagged envelope: a non-zero multiple of 64 bytes, at most 8 entries (512 bytes), each entry
abi.encode(bytes32 key, bytes32 value)laid end to end; - anything else (empty, other lengths, more than 8 entries): no value for any rule.
Rules:
- A key is
keccak256("<NAMESPACE>:<NAME>"), like a rejection code (ADR-005). Two keys are defined by the reference hooks:keccak256("AREX:DELIVERY_ID")(value: theuint256number of a tracked delivery, read byDeliveryHook) andkeccak256("AREX:DOCUMENT_REF")(value: thebytes32reference of the movement's document, read byTraceabilityHook). Implementers add keys under their own namespace. - A rule reads only its own key, takes the first entry with it, and ignores every other entry. A key SHOULD appear at most once.
- An envelope is never 32 bytes long, so a rule written before this record reads it as "no value"; a sender MUST NOT
send an envelope to a token whose rules that read
datado not all read envelopes. - Reading never reverts: a malformed envelope is "no value".
- A value's meaning is the key's, never the rule's, so the same key means the same thing to every rule that reads it.
Argument
The envelope gives each value one meaning on the ledger, so an auditor reading a transfer can tell a delivery number from a document without asking the platform that sent it. It needs no change to the core token or the interfaces, and legacy 32-byte data keeps working because no envelope is 32 bytes long. Keys follow the convention codes already use, so the set stays open without a registry.
Consequences
Positive
- A tracked delivery's settlement can carry its document in the same transfer; the trace entry keeps the document and the delivery is settled by its number.
- New rules that read
dataadd a key instead of competing for the same 32 bytes.
Negative
- Up to 512 bytes of
dataper transfer (calldata cost), and a short loop per reading rule. - Older rule instances read envelopes as "no value"; a sender must know which instances a token runs (a rule descriptor
can say so; see
interop/RULE-DESCRIPTOR.md).
Obligations it creates
- A rule that reads
datadocuments which keys it reads and what their values mean. - Senders build envelopes only for tokens whose
data-reading rules all read them.
Implementation and evidence
| What | Where |
|---|---|
| Reader | arex-contracts contracts/reference/hooks/ARexTransferData.sol (library: valueOf(data, key), never reverting) |
| Rules | DeliveryHook (AREX:DELIVERY_ID) and TraceabilityHook version 2 (AREX:DOCUMENT_REF), release 2.0.0-alpha.7 |
| Tests | arex-contracts test/foundry/TransferDataEnvelope.t.sol: one transfer settles a delivery and records its document; legacy 32-byte data unchanged; malformed envelopes read as no value; fuzzed |
| First consumer | A-REX Platform: a tracked delivery's hand-over with a document, when both rules run 2.0.0-alpha.7 |