Appearance
HookOn
Purpose: selects which transaction types cause a hook to fire, as a 256-bit field where each bit position corresponds to a TxType value.
Amendment gating: before featureHookOnV2_1, sfHookOn is mandatory on create — omitting it is rejected.
Once featureHookOnV2_1 is enabled, a create/install/update may supply eithersfHookOn alone or the sfHookOnIncoming/sfHookOnOutgoing pair, but never both — see HookOnIncoming / HookOnOutgoing for the mutual-exclusivity rule and confirming test cases. featureHookOnV2 is unsupported in the current release.
Runtime semantics — the bit layout. The active-high/active-low split is easy to get backwards, so it's worth spelling out precisely.
In words: for every transaction type except ttHOOK_SET (22), a 0 bit means "fire on this type" and a 1 bit means "don't fire" (active-low). For ttHOOK_SET specifically, the sense is flipped: a 1 bit means "fire on SetHook" and 0 (the default, since HookOn defaults to all-zero) means "don't fire on SetHook". UINT256_BIT is a precomputed table of 256 one-hot uint256 values.
Effective value resolution (hookObj vs hookDef). A hook's live HookOn value is not read from a single field — it resolves with this precedence: first the entry's own directional field on obj if present, then a generic HookOn on obj, then the same directional field on def, then a generic HookOn on def, and finally zero.
obj is the per-account sfHook entry in ltHOOK; def is the shared ltHOOK_DEFINITION; field is sfHookOnIncoming or sfHookOnOutgoing (direction is resolved by the caller — see below). So a generic HookOn set on the account's hook entry overrides direction-specific values from the definition, and a generic HookOn on the definition is the last fallback before an implicit zero. This avoids storing a value on ltHOOK when it's identical to the definition's default.
Worked example — fire only on ttPAYMENT (0), never on ttHOOK_SET (22):
Start from all-1s (nothing fires) and clear bit 0 (arm ttPAYMENT), leaving bit 22 at 0 (so ttHOOK_SET stays off, matching the default). The result is 0xFFF...FFE — all fs except the last hex digit, which is e (1110, clearing only bit 0):
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffbffffeThis value (used as HookOnOutgoing) has zero-bit positions {0, 22} — bit 0 clear (fires on ttPAYMENT), bit 22 clear (does not fire on ttHOOK_SET). A paired HookOnIncoming value clearing bits {22, 99} — ttINVOKE = 99 — achieves the same reasoning applied to ttINVOKE instead of ttPAYMENT.
Common mistakes:
- Treating
HookOnas active-high uniformly. It is active-low for every transaction type exceptttHOOK_SET, which is active-high. - Forgetting that a generic
HookOnon thesfHookobject silently overrides both directions even if the definition has distinctHookOnIncoming/HookOnOutgoingvalues — installing a hook withHookOn = 0at theltHOOKlevel makes it fire on both incoming and outgoing traffic regardless of the definition's split values. - Assuming
HookOn == 0disables the hook entirely. It means "fire on everything exceptSetHook" — the least restrictive, not the most restrictive, setting.