Store
Store Hooks

Store hooks

Store hooks allow you to react to state changes onchain.

There are many ways to interact with tables, but they all boil down to four types of state changes:

  1. A full record (static and dynamic length data) is set (SetRecord)
  2. A part of the static length data of a record is changed (SpliceStaticData)
  3. A part of the dynamic length data of a record is changed (SpliceDynamicData)
  4. A full record is deleted (DeleteRecord)

By registering a store hook contract on a table, arbitrary logic can be triggered before or after each of these state changes.

Store hook contract

A store hook contract is a contract that implements the IStoreHook interface.

After the store hook contract is registered, Store calls the enabled hook methods on each state change.

Example: a hook that creates a lookup table of existing values in a table

import { StoreHook } from "@latticexyz/store/src/StoreHook.sol";
import { Exists } from "./codegen/tables/Exists.sol";
 
contract CustomHook is StoreHook {
  function onBeforeSetRecord(
    ResourceId tableId,
    bytes32[] memory keyTuple,
    bytes memory staticData,
    PackedCounter encodedLengths,
    bytes memory dynamicData,
    FieldLayout
  ) public override {
    Exists.set(keccak256(abi.encodePacked(staticData, encodedLengths, dynamicData)), true);
  }
}

Hook registration

Hooks are registered via the StoreCore library or IStore interface. It is possible to select the type of state change that should trigger a hook and the time (before or after) with a bitmap passed during the registration.

@latticexyz/store/src/storeHookTypes.sol
/// @dev Flag to enable the `onBeforeSetRecord` hook.
uint8 constant BEFORE_SET_RECORD = 1 << 0;
 
/// @dev Flag to enable the `afterSetRecord` hook.
uint8 constant AFTER_SET_RECORD = 1 << 1;
 
/// @dev Flag to enable the `beforeSpliceStaticData` hook.
uint8 constant BEFORE_SPLICE_STATIC_DATA = 1 << 2;
 
/// @dev Flag to enable the `afterSpliceStaticData` hook.
uint8 constant AFTER_SPLICE_STATIC_DATA = 1 << 3;
 
/// @dev Flag to enable the `beforeSpliceDynamicData` hook.
uint8 constant BEFORE_SPLICE_DYNAMIC_DATA = 1 << 4;
 
/// @dev Flag to enable the `afterSpliceDynamicData` hook.
uint8 constant AFTER_SPLICE_DYNAMIC_DATA = 1 << 5;
 
/// @dev Flag to enable the `beforeDeleteRecord` hook.
uint8 constant BEFORE_DELETE_RECORD = 1 << 6;
 
/// @dev Flag to enable the `afterDeleteRecord` hook.
uint8 constant AFTER_DELETE_RECORD = 1 << 7;
 

Example: registering a custom store hook that is triggered before a record is set and before a record is deleted

 
import { StoreCore } from "@latticexyz/store/src/StoreCore.sol";
import { Resourceid } from "@latticexyz/store/src/ResourceId.sol";
import {
  BEFORE_SET_RECORD,
  AFTER_DELETE_RECORD
} from "@latticexyz/store/src/storeHookTypes.sol";
 
ResourceId TABLE_ID = ... // table ID to watch for state changes
 
StoreCore.registerStoreHook({
  tableId: TABLE_ID,
  hookAddress: address(new CustomHook()),
  enabledHooksBitmap: BEFORE_SET_RECORD | AFTER_SET_RECORD
});