Entscheidungsprotokolle · ADR-005
ADR-005: bytes32 rejection codes instead of strings or hook-side custom errors
A hook refuses by returning a namespaced bytes32 code; the token reverts with ARexHookRejected(hook, code) and exposes the same code before the transaction through transferCheck.
- Dokument-ID
- ADR-005
- Datum
- 2026-09-21
- Quelle
- docs/adr/ADR-005.md
| Field | Value |
|---|---|
| ID | ADR-005 |
| Subject area | Error model |
| Status | Accepted |
| Date | 2026-09-21 |
| Owners | A-REX Contributors |
| Related requirements | FR-09 (SDD §3) |
| Supersedes / superseded by | — |
Context
A farmer's wallet has to say why a transfer failed, an indexer has to count refusals by reason, and a test has to compare them. Free-form revert strings are expensive, untranslatable and not indexable. If hooks reverted with their own custom errors, a view that asks several hooks, such as canTransfer, could not compose their answers.
Issue
How does a hook communicate a refusal so that the token, user interfaces and indexers can act on it?
Assumptions
- Front-ends must explain refusals in the user's language.
- Indexers and tests must compare refusals exactly.
Constraints
checkmust be a view so that pre-flight checks can call it.
Alternatives considered
| Alternative | For | Against | Outcome |
|---|---|---|---|
| Free-form revert strings | Readable in explorers | Costly, not indexable, not translatable, not composable | Rejected |
| Hooks revert with custom errors | Typed | check could not be composed in a view across hooks | Rejected |
bytes32 codes keccak256("NAMESPACE:NAME"); token reverts with ARexHookRejected(hook, code) | Cheap, comparable, indexable, open to new namespaces | Needs a dictionary to show text | Accepted |
Decision
check returns bytes32; zero means allowed. Codes are keccak256("NAMESPACE:NAME"); the recommended AREX: codes live in ARexCodes. The token reverts with ARexHookRejected(address hook, bytes32 code) and exposes transferCheck, which returns the first code, in the same order as the transaction.
Argument
A 32-byte code is the cheapest value that is both collision-resistant and open to anyone's namespace. Naming the rejecting hook in the error tells an operator which rule to look at without a trace.
Consequences
Positive
- Wallets can show a translated reason before sending a transaction.
- Issuers add codes without coordinating with anyone.
Negative
- Front-ends need a code dictionary; unknown codes show as hashes.
Obligations it creates
transferCheckand the transaction evaluate in the same order (regression test).- Hooks document their codes.
Implementation and evidence
| What | Where |
|---|---|
| Codes | contracts/standard/ARexCodes.sol |
| Error | IARexHook.ARexHookRejected |
| Same order in view and transaction | test/standard/ARexToken.test.js: reports eligibility before batch existence, like _update |