Skip to content

SetHook Operations: Field Matrix ​

Every element of a SetHook transaction's sfHooks array is one sfHook object, and every sfHook object is interpreted as exactly one of six operations — hsoCREATE, hsoINSTALL, hsoUPDATE, hsoDELETE, hsoNSDELETE, or hsoNOOP — inferred purely from which of the object's 12 possible fields are present. This page is the authoritative per-operation map of which fields are required, optional, or forbidden.

How the operation is inferred ​

The operation is inferred roughly as follows:

In order: both sfHookHash and sfCreateCode present → hsoINVALID. Only sfHookHash → hsoINSTALL. Only sfCreateCode → hsoCREATE if non-empty, hsoDELETE if empty. Neither present, and every other field also absent → hsoNOOP. Otherwise: hsoNSDELETE if sfHookNamespace is present and the hsfNSDELETE bit is set in sfFlags, else hsoUPDATE.

The subtle NOOP condition. The sfHookOn term inside the NOOP check is !(hasOn || (hasOutgoing && hasIncoming)) — note this is not symmetric between the plain field and the directional pair. sfHookOn alone disqualifies NOOP the instant it's present. But sfHookOnOutgoing and sfHookOnIncoming only disqualify NOOP when both are present together — if an sfHook object carries sfHookOnOutgoing (or sfHookOnIncoming) alone, with every other field absent, the object still infers as hsoNOOP and that lone directional field is silently ignored (the apply loop's NOOP case simply copies the old hook through unchanged, never reading the field at all). This is easy to trigger by accident: submitting {HookOnOutgoing: <value>} with nothing else in the object does not update anything.

sfHooks-array-level checks that apply before any per-object inference gate the whole transaction regardless of what individual objects infer to: featureHooks must be enabled (temDISABLED otherwise); the transaction's own top-level sfFlags must be clean of non-universal bits when fixInvalidTxFlags is enabled (temINVALID_FLAG); sfHooks must be present, non-empty, and at most hook::maxHookChainLength() = 10 entries (temMALFORMED); every element must be an sfHook object; a present sfCreateCode may not exceed hook::maxHookWasmSize() = 65,535 bytes regardless of operation; every non-blank sfHook object may only contain the 12 fields listed below — anything else is temMALFORMED; and at least one sfHook object in the array must be non-blank (HOOKS_ARRAY_BLANK).

The matrix ​

FieldCREATEINSTALLUPDATEDELETENSDELETENOOP
sfHookHashF [^hash-create]RF [^hash-update]FFF
sfCreateCodeR (non-empty)F [^code-other]F [^code-other]R (empty) [^code-delete]FF
sfHookGrantsOOOFFF
sfHookNamespaceROO [^ns-update]FRF
sfHookParametersOOOFFF
sfHookOnR/O [^onv2]OOFFF
sfHookOnIncomingR/O [^onv2]O [^onv2-gate]O [^onv2-gate]FFF
sfHookOnOutgoingR/O [^onv2]O [^onv2-gate]O [^onv2-gate]FFF
sfHookCanEmitO [^canemit]O [^canemit]O [^canemit]FFF
sfHookApiVersionR (must be 0)FFFFF
sfHookNameO [^name]O [^name]O [^name]FFF
sfFlagsO [^flags-create]O [^flags-install]O [^flags-update]R [^flags-delete]R [^flags-nsdelete]F

R = required, O = optional (subject to the cited validation), F = forbidden (presence is rejected, or — where noted — reclassifies the object as a different operation before the forbidding check would even run). NOOP forbids all 12 fields by construction: any field present other than a directional HookOn field presented alone (see "subtle NOOP condition" above) makes the object something other than NOOP.

[^hash-create]: Presence together with non-empty sfCreateCode infers hsoINVALID, not hsoCREATE — see inferOperation above. [^hash-update]: sfHookHash presence always infers hsoINSTALL (or hsoINVALID with sfCreateCode) — an object with sfHookHash is never classified hsoUPDATE. [^code-other]: Presence (even as an empty blob) reclassifies the object as hsoCREATE/hsoDELETE, never hsoINSTALL/hsoUPDATE. [^code-delete]: Must be present as an empty blob; a non-empty blob infers hsoCREATE instead. [^ns-update]: If both sfHookNamespace and hsfNSDELETE (in sfFlags) are present, the object infers hsoNSDELETE, not hsoUPDATE — see HookNamespace. [^onv2]: Before featureHookOnV2_1: sfHookOn alone is required. With featureHookOnV2_1: either sfHookOn alone, or both sfHookOnIncoming and sfHookOnOutgoing together (and they must differ) — never a mix of sfHookOn with either directional field. See HookOnIncoming / HookOnOutgoing. [^onv2-gate]: Directional HookOn fields are only valid when featureHookOnV2_1 is enabled and both sfHookOnIncoming and sfHookOnOutgoing are supplied together with different values. See HookOnIncoming / HookOnOutgoing. [^canemit]: Gated globally: any non-blank sfHook object carrying sfHookCanEmit without featureHookCanEmit enabled is temDISABLED, independent of which operation it is. See HookCanEmit. [^name]: Gated globally: any non-blank sfHook object carrying sfHookName without featureNamedHooks enabled is temDISABLED, and the value must pass SetHook::validateHookName (4–16 bytes or empty, valid UTF-8). See HookName. [^flags-create]: hsfOVERRIDE is required only if a hook already occupies the target chain slot (apply-time tecREQUIRES_FLAG otherwise); no other bit is restricted by validateHookSetEntry. [^flags-install]: Same hsfOVERRIDE rule as create; no other bit restricted. [^flags-update]: hsfOVERRIDE is forbidden outright; hsfNSDELETE is accepted by inferOperation/validation only in combination with sfHookNamespace — but that exact combination is always classified hsoNSDELETE, not hsoUPDATE (see [^ns-update]), so a validated hsoUPDATE can never actually carry hsfNSDELETE. See Flags. [^flags-delete]: Required; must include hsfOVERRIDE; allowed bits are limited to hsfOVERRIDE | hsfNSDELETE | hsfCOLLECT. [^flags-nsdelete]: Required; must equal exactly hsfNSDELETE, no other bit.

Per-operation detail ​

hsoCREATE ​

  • sfCreateCode non-empty, ≤ 65,535 bytes; guard-validated and WasmEdge-smoke-tested (see CreateCode).
  • sfHookNamespace required (NAMESPACE_MISSING otherwise).
  • sfHookApiVersion required, must equal 0 (API_MISSING/API_INVALID).
  • Exactly one of sfHookOn alone, or sfHookOnIncoming + sfHookOnOutgoing together (distinct values, requires featureHookOnV2_1); before the amendment, sfHookOn alone is mandatory.
  • sfHookGrants/sfHookParameters, if present, validated by validateHookGrants/validateHookParams and become the new definition's default set — not merged against anything, since there is no prior definition.
  • sfHookName, if present, validated by validateHookName; requires featureNamedHooks.
  • sfHookCanEmit, if present, requires featureHookCanEmit; no additional shape validation.
  • sfFlags: hsfOVERRIDE required only to replace an already-installed hash at the same chain slot.
  • If the WASM's hash already matches an existing ltHOOK_DEFINITION, the whole operation [[fallthrough]]s into hsoINSTALL's apply logic — see HookHash.

hsoINSTALL ​

  • sfHookHash required; preclaim requires the referenced ltHOOK_DEFINITION to already exist (terNO_HOOK).
  • sfHookApiVersion forbidden (API_ILLEGAL).
  • sfHookGrants/sfHookParameters, if present, validated the same way as create; parameters go through the three-way merge described in HookParameters (starting from an empty prior set).
  • sfHookNamespace, sfHookOn/sfHookOnIncoming/sfHookOnOutgoing, sfHookCanEmit, sfHookName are accepted. When featureHookOnV2_1 is enabled, supplied HookOn fields must pass the same pair, mutual-exclusion, and unequal-value checks as create; at apply time each is stored on the entry only if it differs from the target definition's resolved value (the same storage-optimization pattern as hsoUPDATE).
  • sfFlags: hsfOVERRIDE required only if a hook already occupies the target chain slot (tecREQUIRES_FLAG otherwise).

hsoUPDATE ​

  • Inferred whenever none of sfHookHash/sfCreateCode are present, at least one other field is present, and it isn't the specific (sfHookNamespace present, hsfNSDELETE set) pair that infers hsoNSDELETE instead.
  • sfHookApiVersion forbidden (API_ILLEGAL).
  • hsfOVERRIDE forbidden unconditionally; hsfNSDELETE is only accepted by validation together with sfHookNamespace, but that combination is never actually classified hsoUPDATE (see [^flags-update] above) — so in effect hsoUPDATE can carry neither control bit.
  • sfHookGrants/sfHookParameters: absent → prior value carried over unchanged; explicit empty array → cleared; non-empty → replaces (grants) or three-way-merges (parameters, see HookParameters).
  • sfHookNamespace, sfHookOn/sfHookOnIncoming/sfHookOnOutgoing, sfHookCanEmit, sfHookName: all optional, each stored on the entry only if it differs from the resolved definition value, else removed from the entry to fall back to the default. This has been confirmed by individually setting each field, then individually resetting each back to its definition default.
  • sfFlags, if present, is stored verbatim (raw, not the hsfOVERRIDE/hsfNSDELETE-stripped value used by create/install — see Flags for why this has no observable effect here).

hsoDELETE ​

  • Only sfCreateCode (empty) and sfFlags may be present; every other field — grants, parameters, HookOn/HookOnIncoming/HookOnOutgoing, HookCanEmit, HookApiVersion, HookNamespace, HookName — is forbidden (DELETE_FIELD). This has been verified exhaustively.
  • sfFlags required, must include hsfOVERRIDE (OVERRIDE_MISSING otherwise); allowed bits limited to hsfOVERRIDE | hsfNSDELETE | hsfCOLLECT (FLAGS_INVALID otherwise).
  • At apply time: places a blank sfHook at that chain position; if a hook previously occupied the slot, its ltHOOK_DEFINITION reference count is decremented (and the definition erased if it reaches zero) — see HookHash.
  • If hsfNSDELETE is also set, the prior occupant's namespace is queued for destruction as a side effect — see HookNamespace.

hsoNSDELETE ​

  • Only sfHookNamespace and sfFlags may be present, and both are required; every other field is forbidden (NSDELETE_FIELD). This has been verified exhaustively.
  • sfFlags must equal exactly hsfNSDELETE — no other bit, including hsfOVERRIDE or hsfCOLLECT (NSDELETE_FLAGS).
  • Does not touch the hook chain itself — the sfHook array slot this entry occupies is otherwise treated like NOOP for chain purposes (the prior hook at that position, if any, is carried through unchanged); only the named namespace's Hook State is affected. See HookNamespace for the full deletion mechanics, including the fixNSDelete-gated partial-delete (tesPARTIAL) and owner-reserve-refund behavior.

hsoNOOP ​

  • All 12 fields absent (subject to the directional-HookOn quirk described above). A wholly blank sfHook object (getCount() == 0) is skipped even earlier, in the preflight per-array-element loop, and doesn't reach inferOperation at all.
  • At apply time, the chain slot is left exactly as it was (existing hook copied through unchanged, or a blank placeholder if none existed).

Amendment gates ​

  • featureHooks gates the entire transaction type.
  • featureHookOnV2_1 governs the directional HookOn form: use plain sfHookOn, or use the sfHookOnIncoming/sfHookOnOutgoing pair with different values. See HookOnIncoming / HookOnOutgoing.
  • featureHookCanEmit and featureNamedHooks each gate their field globally, on every non-blank sfHook object regardless of inferred operation.