Registros de decisão · ADR-004
ADR-004: One hook interface, flags cached at registration, ordered evaluation
Every rule is an independent IARexHook contract; the token calls the hooks in registration order, using flags cached at registration, and stops at the first rejection.
- ID do documento
- ADR-004
- Data
- 2026-09-21
- Fonte
- docs/adr/ADR-004.md
| Field | Value |
|---|---|
| ID | ADR-004 |
| Subject area | Extensibility |
| Status | Accepted |
| Date | 2026-09-21 |
| Owners | A-REX Contributors |
| Related requirements | FR-07, FR-08, NFR-01, NFR-02, NFR-10 (SDD §3) |
| Supersedes / superseded by | — |
Context
Perishability, harvest seasons, custody during delivery, quotas, sanctions and document requirements all decide whether units may move, and no issuer needs all of them. The earlier design probed each module for five capability interfaces with supportsInterface on every transfer, so the cost grew as ids × modules × probes before any rule ran, and a module written for one token could not run on another.
Issue
How do rules attach to the token so that cost stays linear, order is auditable and one rule contract can serve many tokens?
Assumptions
- Rules differ per issuer; none needs all of them.
- Gas per transfer must be linear in the number of rules actually attached.
- The order in which rules run is a governance decision that must be visible.
Constraints
- Rules must not be able to change balances or re-enter the token.
- The token must stay under the EIP-170 size limit.
Alternatives considered
| Alternative | For | Against | Outcome |
|---|---|---|---|
| Several capability interfaces probed per transfer | Fine-grained | External calls before any logic; not portable between tokens | Rejected |
ERC-3643 ICompliance adapted to ERC-1155 | Known to security-token issuers | ERC-20 semantics, no batch id, no transfer data | Rejected |
Single IARexHook (hookFlags, check, onExecuted, canSend, canReceive) with cached flags | One call per interested hook; portable; ordered | Hooks must follow a strict callback contract | Accepted |
Decision
A conformant token keeps an ordered list of hooks and caches each hook's hookFlags() when it is registered. For every affected id it calls check on every CHECK hook, in order, and reverts with ARexHookRejected(hook, code) at the first non-zero code; after balances change it calls onExecuted on every EXECUTED hook; ELIGIBILITY hooks answer canSend and canReceive. A hook learns which token is calling from msg.sender, so one deployment serves many tokens.
Argument
Caching the flags turns "which capabilities does this module have" from a per-transfer question into a one-time one. A single callback contract is also what makes a rule portable: any conformant token can register any conformant hook.
Consequences
Positive
- Measured cost of about 25–30k gas per CHECK hook on a transfer, linear in the number of hooks (SDD §10).
- Rules can be added and removed without touching the token.
Negative
- A buggy or malicious hook can block transfers until it is removed; hook registration is a trusted act.
- Changing a hook's flags requires re-registration.
Obligations it creates
HOOK_ADMIN_ROLEbelongs behind a timelock so that hook changes are visible before they apply.- Hooks must not revert for policy (ADR-005) and must return OK for forced transfers unless they govern enforcement (ADR-012).
- The token holds a reentrancy lock across the whole sequence.
Implementation and evidence
| What | Where |
|---|---|
| Pipeline | ARexToken._update, registerHook, removeHook |
| Order, caching, rejection, data, reentrancy | test/standard/ARexToken.test.js: registers hooks in order, caches flags and removes preserving order, stops at the first rejecting hook…, forwards transfer data to hooks, blocks reentrancy from onExecuted |
| Gas per hook | scripts/gas-report-standard.js |