Hooks: On-Chain Events

Primodium hooks are contracts that are defined to react to specific parts of the codebase. Hooks can be thought of as subsystems that are frequently called before or after core system functions.

Hooks allow us to decouple the core logic from secondary and reactionary logic. Hooks are also a workaround to smart contract size limits set by EIP-170 (opens in a new tab), which prevents adding features by directly increasing the size of core system contracts.

In the open MUD standard, hooks can react to both system calls and tables. Tables hooks are used throughout Primodium contracts, unlike system hooks are replaced with subsystems and modifiers.

Table Hooks

Table Hooks allows us to define reactionary logic to changes to a table values. They are based on store hooks (opens in a new tab) as implemented in the open MUD standard.

Table Hooks allow us to define tables with values dependant on other tables but keep their logic decoupled. The most common hook is the claimResources subsystem, which updates resource counts to their up-to-date values.

The following is an example of another hook subscribed to the Score table and updates the AllianceScore table whenever an alliance member score is updated.

/packages/contracts/src/hooks/storeHooks/OnScore_Alliance_Score.sol
/// @title OnPoints_Alliance_Points - Updates alliance points based on player points.
contract OnPoints_Alliance_Points is StoreHook {
  constructor() {}
 
  /// @dev This function is called before splicing static data.
  /// @param keyTuple The key tuple of the player points.
  /// @param start The start position of the data.
  /// @param data The data to be processed.
  function onBeforeSpliceStaticData(
    ResourceId,
    bytes32[] memory keyTuple,
    uint48 start,
    bytes memory data
  ) public override {
    bytes32 playerEntity = keyTuple[0];
    uint8 pointsType = uint8(uint256(keyTuple[1]));
    bytes32 allianceEntity = PlayerAlliance.getAlliance(playerEntity);
 
    if (allianceEntity == 0) return;
 
    bytes memory newPointsRaw = SliceInstance.toBytes(SliceLib.getSubslice(data, start));
    uint256 newPoints = abi.decode(newPointsRaw, (uint256));
    uint256 oldPoints = Points.get(playerEntity, pointsType);
    uint256 alliancePoint = Points.get(allianceEntity, pointsType);
    uint256 prevContribution = AlliancePointContribution.get(allianceEntity, pointsType, playerEntity);
 
    if (newPoints > oldPoints) {
      uint256 pointsDiff = newPoints - oldPoints;
      Points.set(allianceEntity, pointsType, alliancePoint + pointsDiff);
      AlliancePointContribution.set(allianceEntity, pointsType, playerEntity, prevContribution + pointsDiff);
    } else {
      uint256 pointsDiff = oldPoints - newPoints;
 
      if (pointsDiff > alliancePoint) {
        Points.set(allianceEntity, pointsType, 0);
      } else Points.set(allianceEntity, pointsType, alliancePoint - pointsDiff);
 
      if (pointsDiff > prevContribution) {
        AlliancePointContribution.set(allianceEntity, pointsType, playerEntity, 0);
      } else AlliancePointContribution.set(allianceEntity, pointsType, playerEntity, alliancePoint - pointsDiff);
    }
  }
}