Skip to content

Releases: latticexyz/mud

@latticexyz/[email protected]

30 Sep 22:47
12a9eb1
Compare
Choose a tag to compare
Pre-release
@latticexyz/[email protected]

[email protected]

25 Sep 18:43
bdf2882
Compare
Choose a tag to compare
[email protected] Pre-release
Pre-release
[email protected]

[email protected]

25 Sep 18:43
bdf2882
Compare
Choose a tag to compare
[email protected] Pre-release
Pre-release
[email protected]

[email protected]

25 Sep 18:43
bdf2882
Compare
Choose a tag to compare
[email protected] Pre-release
Pre-release

Minor Changes

  • #1482 07dd6f32 Thanks @alvrs! - Renamed all occurrences of schema where it is used as "value schema" to valueSchema to clearly distinguish it from "key schema".
    The only breaking change for users is the change from schema to valueSchema in mud.config.ts.

    // mud.config.ts
    export default mudConfig({
      tables: {
        CounterTable: {
          keySchema: {},
    -     schema: {
    +     valueSchema: {
            value: "uint32",
          },
        },
      }
    }
  • #1483 83583a50 Thanks @holic! - Templates now use out for their forge build artifacts, including ABIs. If you have a project created from a previous template, you can update your packages/contracts/package.json with:

    - "build:abi": "rimraf abi && forge build --extra-output-files abi --out abi --skip test script MudTest.sol",
    - "build:abi-ts": "mud abi-ts --input 'abi/IWorld.sol/IWorld.abi.json' && prettier --write '**/*.abi.json.d.ts'",
    + "build:abi": "forge clean && forge build --skip test script",
    + "build:abi-ts": "mud abi-ts && prettier --write '**/*.abi.json.d.ts'",

    And your packages/client/src/mud/setupNetwork with:

    - import IWorldAbi from "contracts/abi/IWorld.sol/IWorld.abi.json";
    + import IWorldAbi from "contracts/out/IWorld.sol/IWorld.abi.json";
  • #1473 92de5998 Thanks @holic! - Bump Solidity version to 0.8.21

  • #1354 331dbfdc Thanks @dk1a! - We've updated Store events to be "schemaless", meaning there is enough information in each event to only need to operate on the bytes of each record to make an update to that record without having to first decode the record by its schema. This enables new kinds of indexers and sync strategies.

    As such, we've replaced blockStorageOperations# Change Log with storedBlockLogs# Change Log, a stream of simplified Store event logs after they've been synced to the configured storage adapter. These logs may not reflect exactly the events that are on chain when e.g. hydrating from an indexer, but they will still allow the client to "catch up" to the on-chain state of your tables.

  • #1558 bfcb293d Thanks @alvrs! - What used to be known as ephemeral table is now called offchain table.
    The previous ephemeral tables only supported an emitEphemeral method, which emitted a StoreSetEphemeralRecord event.

    Now offchain tables support all regular table methods, except partial operations on dynamic fields (push, pop, update).
    Unlike regular tables they don't store data on-chain but emit the same events as regular tables (StoreSetRecord, StoreSpliceStaticData, StoreDeleteRecord), so their data can be indexed by offchain indexers/clients.

    - EphemeralTable.emitEphemeral(value);
    + OffchainTable.set(value);

Patch Changes

  • #1318 ac508bf1 Thanks @holic! - Renamed the default filename of generated user types from Types.sol to common.sol and the default filename of the generated table index file from Tables.sol to index.sol.

    Both can be overridden via the MUD config:

    export default mudConfig({
      /** Filename where common user types will be generated and imported from. */
      userTypesFilename: "common.sol",
      /** Filename where codegen index will be generated. */
      codegenIndexFilename: "index.sol",
    });

    Note: userTypesFilename was renamed from userTypesPath and .sol is not appended automatically anymore but needs to be part of the provided filename.

    To update your existing project, update all imports from Tables.sol to index.sol and all imports from Types.sol to common.sol, or override the defaults in your MUD config to the previous values.

    - import { Counter } from "../src/codegen/Tables.sol";
    + import { Counter } from "../src/codegen/index.sol";
    - import { ExampleEnum } from "../src/codegen/Types.sol";
    + import { ExampleEnum } from "../src/codegen/common.sol";
  • #1581 cea754dd Thanks @alvrs! - - The external setRecord and deleteRecord methods of IStore no longer accept a FieldLayout as input, but load it from storage instead.
    This is to prevent invalid FieldLayout values being passed, which could cause the onchain state to diverge from the indexer state.
    However, the internal StoreCore library still exposes a setRecord and deleteRecord method that allows a FieldLayout to be passed.
    This is because StoreCore can only be used internally, so the FieldLayout value can be trusted and we can save the gas for accessing storage.

    interface IStore {
      function setRecord(
        ResourceId tableId,
        bytes32[] calldata keyTuple,
        bytes calldata staticData,
        PackedCounter encodedLengths,
        bytes calldata dynamicData,
    -   FieldLayout fieldLayout
      ) external;
    
      function deleteRecord(
        ResourceId tableId,
        bytes32[] memory keyTuple,
    -   FieldLayout fieldLayout
      ) external;
    }
    • The spliceStaticData method and Store_SpliceStaticData event of IStore and StoreCore no longer include deleteCount in their signature.
      This is because when splicing static data, the data after start is always overwritten with data instead of being shifted, so deleteCount is always the length of the data to be written.

      event Store_SpliceStaticData(
        ResourceId indexed tableId,
        bytes32[] keyTuple,
        uint48 start,
      - uint40 deleteCount,
        bytes data
      );
      
      interface IStore {
        function spliceStaticData(
          ResourceId tableId,
          bytes32[] calldata keyTuple,
          uint48 start,
      -   uint40 deleteCount,
          bytes calldata data
        ) external;
      }
    • The updateInField method has been removed from IStore, as it's almost identical to the more general spliceDynamicData.
      If you're manually calling updateInField, here is how to upgrade to spliceDynamicData:

      - store.updateInField(tableId, keyTuple, fieldIndex, startByteIndex, dataToSet, fieldLayout);
      + uint8 dynamicFieldIndex = fieldIndex - fieldLayout.numStaticFields();
      + store.spliceDynamicData(tableId, keyTuple, dynamicFieldIndex, uint40(startByteIndex), uint40(dataToSet.length), dataToSet);
    • All other methods that are only valid for dynamic fields (pushToField, popFromField, getFieldSlice)
      have been renamed to make this more explicit (pushToDynamicField, popFromDynamicField, getDynamicFieldSlice).

      Their fieldIndex parameter has been replaced by a dynamicFieldIndex parameter, which is the index relative to the first dynamic field (i.e. dynamicFieldIndex = fieldIndex - numStaticFields).
      The FieldLayout parameter has been removed, as it was only used to calculate the dynamicFieldIndex in the method.

      interface IStore {
      - function pushToField(
      + function pushToDynamicField(
          ResourceId tableId,
          bytes32[] calldata keyTuple,
      -   uint8 fieldIndex,
      +   uint8 dynamicFieldIndex,
          bytes calldata dataToPush,
      -   FieldLayout fieldLayout
        ) external;
      
      - function popFromField(
      + function popFromDynamicField(
          ResourceId tableId,
          bytes32[] calldata keyTuple,
      -   uint8 fieldIndex,
      +   uint8 dynamicFieldIndex,
          uint256 byteLengthToPop,
      -   FieldLayout fieldLayout
        ) external;
      
      - function getFieldSlice(
      + function getDynamicFieldSlice(
          ResourceId tableId,
          bytes32[] memory keyTuple,
      -   uint8 fieldIndex,
      +   uint8 dynamicFieldIndex,
      -   FieldLayout fieldLayout,
          uint256 start,
          uint256 end
        ) external view returns (bytes memory data);
      }
    • IStore has a new getDynamicFieldLength length method, which returns the byte length of the given dynamic field and doesn't require the FieldLayout.

      IStore {
      + function getDynamicFieldLength(
      +   ResourceId tableId,
      +   bytes32[] memory keyTuple,
      +   uint8 dynamicFieldIndex
      + ) external view returns (uint256);
      }
      
    • IStore now has additional overloads for getRecord, getField, getFieldLength and setField that don't require a FieldLength to be passed, but instead load it from storage.

    • IStore now exposes setStaticField and setDynamicField to save gas by avoiding the dynamic in...

Read more

@latticexyz/[email protected]

25 Sep 18:43
bdf2882
Compare
Choose a tag to compare
Pre-release

Major Changes

  • #1606 77dce993 Thanks @holic! - Moves World interfaces and factories files for consistency with our other packages.

    If you import any World interfaces or factories directly, you'll need to update the import path:

    - import { IBaseWorld } from "@latticexyz/world/src/interfaces/IBaseWorld.sol";
    + import { IBaseWorld } from "@latticexyz/world/src/IBaseWorld.sol";
    - import { IBaseWorld } from "@latticexyz/world/src/factories/WorldFactory.sol";
    + import { IBaseWorld } from "@latticexyz/world/src/WorldFactory.sol";
  • #1563 748f4588 Thanks @alvrs! - All World methods now revert if the World calls itself.
    The World should never need to externally call itself, since all internal table operations happen via library calls, and all root system operations happen via delegate call.

    It should not be possible to make the World call itself as an external actor.
    If it were possible to make the World call itself, it would be possible to write to internal tables that only the World should have access to.
    As this is a very important invariance, we made it explicit in a requirement check in every World method, rather than just relying on making it impossible to trigger the World to call itself.

    This is a breaking change for modules that previously used external calls to the World in the installRoot method.
    In the installRoot method, the World can only be called via delegatecall, and table operations should be performed via the internal table methods (e.g. _set instead of set).

    Example for how to replace external calls to world in root systems / root modules (installRoot) with delegatecall:

    + import { revertWithBytes } from "@latticexyz/world/src/revertWithBytes.sol";
    
    - world.grantAccess(tableId, address(hook));
    + (bool success, bytes memory returnData) = address(world).delegatecall(
    +   abi.encodeCall(world.grantAccess, (tableId, address(hook)))
    + );
    
    + if (!success) revertWithBytes(returnData);
  • #1592 c07fa021 Thanks @alvrs! - Tables and interfaces in the world package are now generated to the codegen folder.
    This is only a breaking change if you imported tables or codegenerated interfaces from @latticexyz/world directly.
    If you're using the MUD CLI, the changed import paths are already integrated and no further changes are necessary.

    - import { IBaseWorld } from "@latticexyz/world/src/interfaces/IBaseWorld.sol";
    + import { IBaseWorld } from "@latticexyz/world/src/codegen/interfaces/IBaseWorld.sol";
    
  • #1354 331dbfdc Thanks @dk1a! - We've updated Store events to be "schemaless", meaning there is enough information in each event to only need to operate on the bytes of each record to make an update to that record without having to first decode the record by its schema. This enables new kinds of indexers and sync strategies.

    If you've written your own sync logic or are interacting with Store calls directly, this is a breaking change. We have a few more breaking protocol changes upcoming, so you may hold off on upgrading until those land.

    If you are using MUD's built-in tooling (table codegen, indexer, store sync, etc.), you don't have to make any changes except upgrading to the latest versions and deploying a fresh World.

    • The data field in each StoreSetRecord and StoreEphemeralRecord has been replaced with three new fields: staticData, encodedLengths, and dynamicData. This better reflects the on-chain state and makes it easier to perform modifications to the raw bytes. We recommend storing each of these fields individually in your off-chain storage of choice (indexer, client, etc.).

      - event StoreSetRecord(bytes32 tableId, bytes32[] keyTuple, bytes data);
      + event StoreSetRecord(bytes32 tableId, bytes32[] keyTuple, bytes staticData, bytes32 encodedLengths, bytes dynamicData);
      
      - event StoreEphemeralRecord(bytes32 tableId, bytes32[] keyTuple, bytes data);
      + event StoreEphemeralRecord(bytes32 tableId, bytes32[] keyTuple, bytes staticData, bytes32 encodedLengths, bytes dynamicData);
    • The StoreSetField event is now replaced by two new events: StoreSpliceStaticData and StoreSpliceDynamicData. Splicing allows us to perform efficient operations like push and pop, in addition to replacing a field value. We use two events because updating a dynamic-length field also requires updating the record's encodedLengths (aka PackedCounter).

      - event StoreSetField(bytes32 tableId, bytes32[] keyTuple, uint8 fieldIndex, bytes data);
      + event StoreSpliceStaticData(bytes32 tableId, bytes32[] keyTuple, uint48 start, uint40 deleteCount, bytes data);
      + event StoreSpliceDynamicData(bytes32 tableId, bytes32[] keyTuple, uint48 start, uint40 deleteCount, bytes data, bytes32 encodedLengths);

    Similarly, Store setter methods (e.g. setRecord) have been updated to reflect the data to staticData, encodedLengths, and dynamicData changes. We'll be following up shortly with Store getter method changes for more gas efficient storage reads.

  • #1527 759514d8 Thanks @holic! - Moved the registration of store hooks and systems hooks to bitmaps with bitwise operator instead of a struct.

    - import { StoreHookLib } from "@latticexyz/src/StoreHook.sol";
    + import {
    +   BEFORE_SET_RECORD,
    +   BEFORE_SET_FIELD,
    +   BEFORE_DELETE_RECORD
    + } from "@latticexyz/store/storeHookTypes.sol";
    
      StoreCore.registerStoreHook(
        tableId,
        subscriber,
    -   StoreHookLib.encodeBitmap({
    -     onBeforeSetRecord: true,
    -     onAfterSetRecord: false,
    -     onBeforeSetField: true,
    -     onAfterSetField: false,
    -     onBeforeDeleteRecord: true,
    -     onAfterDeleteRecord: false
    -   })
    +   BEFORE_SET_RECORD | BEFORE_SET_FIELD | BEFORE_DELETE_RECORD
      );
    - import { SystemHookLib } from "../src/SystemHook.sol";
    + import { BEFORE_CALL_SYSTEM, AFTER_CALL_SYSTEM } from "../src/systemHookTypes.sol";
    
      world.registerSystemHook(
        systemId,
        subscriber,
    -   SystemHookLib.encodeBitmap({ onBeforeCallSystem: true, onAfterCallSystem: true })
    +   BEFORE_CALL_SYSTEM | AFTER_CALL_SYSTEM
      );
    
  • #1531 d5094a24 Thanks @alvrs! - - The IStoreHook interface was changed to replace onBeforeSetField and onAfterSetField with onBeforeSpliceStaticData, onAfterSpliceStaticData, onBeforeSpliceDynamicData and onAfterSpliceDynamicData.

    This new interface matches the new StoreSpliceStaticData and StoreSpliceDynamicData events, and avoids having to read the entire field from storage when only a subset of the field was updated
    (e.g. when pushing elements to a field).

    interface IStoreHook {
    - function onBeforeSetField(
    -   bytes32 tableId,
    -   bytes32[] memory keyTuple,
    -   uint8 fieldIndex,
    -   bytes memory data,
    -   FieldLayout fieldLayout
    - ) external;
    
    - function onAfterSetField(
    -   bytes32 tableId,
    -   bytes32[] memory keyTuple,
    -   uint8 fieldIndex,
    -   bytes memory data,
    -   FieldLayout fieldLayout
    - ) external;
    
    + function onBeforeSpliceStaticData(
    +   bytes32 tableId,
    +   bytes32[] memory keyTuple,
    +   uint48 start,
    +   uint40 deleteCount,
    +   bytes memory data
    + ) external;
    
    + function onAfterSpliceStaticData(
    +   bytes32 tableId,
    +   bytes32[] memory keyTuple,
    +   uint48 start,
    +   uint40 deleteCount,
    +   bytes memory data
    + ) external;
    
    + function onBeforeSpliceDynamicData(
    +   bytes32 tableId,
    +   bytes32[] memory keyTuple,
    +   uint8 dynamicFieldIndex,
    +   uint40 startWithinField,
    +   uint40 deleteCount,
    +   bytes memory data,
    +   PackedCounter encodedLengths
    + ) external;
    
    + function onAfterSpliceDynamicData(
    +   bytes32 tableId,
    +   bytes32[] memory keyTuple,
    +   uint8 dynamicFieldIndex,
    +   uint40 startWithinField,
    +   uint40 deleteCount,
    +   bytes memory data,
    +   PackedCounter encodedLengths
    + ) external;
    }
    • All calldata parameters on the IStoreHook interface were changed to memory, since the functions are called with memory from the World.

    • IStore exposes two new functions: spliceStaticData and spliceDynamicData.

      These functions provide lower level access to the operations happening under the hood in setField, pushToField, popFromField and updateInField and simplify handling
      the new splice hooks.

      StoreCore's internal logic was simplified to use the spliceStaticData and spliceDynamicData functions instead of duplicating similar logic in different functions.

    ...

Read more

@latticexyz/[email protected]

25 Sep 18:44
bdf2882
Compare
Choose a tag to compare
Pre-release

Major Changes

  • #1591 251170e1 Thanks @alvrs! - All optional modules have been moved from @latticexyz/world to @latticexyz/world-modules.
    If you're using the MUD CLI, the import is already updated and no changes are necessary.

Patch Changes

@latticexyz/[email protected]

25 Sep 18:43
bdf2882
Compare
Choose a tag to compare
Pre-release
@latticexyz/[email protected]

@latticexyz/[email protected]

25 Sep 18:43
bdf2882
Compare
Choose a tag to compare
Pre-release

Major Changes

  • #1482 07dd6f32 Thanks @alvrs! - Renamed all occurrences of schema where it is used as "value schema" to valueSchema to clearly distinguish it from "key schema".
    The only breaking change for users is the change from schema to valueSchema in mud.config.ts.

    // mud.config.ts
    export default mudConfig({
      tables: {
        CounterTable: {
          keySchema: {},
    -     schema: {
    +     valueSchema: {
            value: "uint32",
          },
        },
      }
    }
  • #1354 331dbfdc Thanks @dk1a! - We've updated Store events to be "schemaless", meaning there is enough information in each event to only need to operate on the bytes of each record to make an update to that record without having to first decode the record by its schema. This enables new kinds of indexers and sync strategies.

    If you've written your own sync logic or are interacting with Store calls directly, this is a breaking change. We have a few more breaking protocol changes upcoming, so you may hold off on upgrading until those land.

    If you are using MUD's built-in tooling (table codegen, indexer, store sync, etc.), you don't have to make any changes except upgrading to the latest versions and deploying a fresh World.

    • The data field in each StoreSetRecord and StoreEphemeralRecord has been replaced with three new fields: staticData, encodedLengths, and dynamicData. This better reflects the on-chain state and makes it easier to perform modifications to the raw bytes. We recommend storing each of these fields individually in your off-chain storage of choice (indexer, client, etc.).

      - event StoreSetRecord(bytes32 tableId, bytes32[] keyTuple, bytes data);
      + event StoreSetRecord(bytes32 tableId, bytes32[] keyTuple, bytes staticData, bytes32 encodedLengths, bytes dynamicData);
      
      - event StoreEphemeralRecord(bytes32 tableId, bytes32[] keyTuple, bytes data);
      + event StoreEphemeralRecord(bytes32 tableId, bytes32[] keyTuple, bytes staticData, bytes32 encodedLengths, bytes dynamicData);
    • The StoreSetField event is now replaced by two new events: StoreSpliceStaticData and StoreSpliceDynamicData. Splicing allows us to perform efficient operations like push and pop, in addition to replacing a field value. We use two events because updating a dynamic-length field also requires updating the record's encodedLengths (aka PackedCounter).

      - event StoreSetField(bytes32 tableId, bytes32[] keyTuple, uint8 fieldIndex, bytes data);
      + event StoreSpliceStaticData(bytes32 tableId, bytes32[] keyTuple, uint48 start, uint40 deleteCount, bytes data);
      + event StoreSpliceDynamicData(bytes32 tableId, bytes32[] keyTuple, uint48 start, uint40 deleteCount, bytes data, bytes32 encodedLengths);

    Similarly, Store setter methods (e.g. setRecord) have been updated to reflect the data to staticData, encodedLengths, and dynamicData changes. We'll be following up shortly with Store getter method changes for more gas efficient storage reads.

  • #1589 f9f9609e Thanks @alvrs! - The argument order on Store_SpliceDynamicData, onBeforeSpliceDynamicData and onAfterSpliceDynamicData has been changed to match the argument order on Store_SetRecord,
    where the PackedCounter encodedLength field comes before the bytes dynamicData field.

    IStore {
      event Store_SpliceDynamicData(
        ResourceId indexed tableId,
        bytes32[] keyTuple,
        uint48 start,
        uint40 deleteCount,
    +   PackedCounter encodedLengths,
        bytes data,
    -   PackedCounter encodedLengths
      );
    }
    
    IStoreHook {
      function onBeforeSpliceDynamicData(
        ResourceId tableId,
        bytes32[] memory keyTuple,
        uint8 dynamicFieldIndex,
        uint40 startWithinField,
        uint40 deleteCount,
    +   PackedCounter encodedLengths,
        bytes memory data,
    -   PackedCounter encodedLengths
      ) external;
    
      function onAfterSpliceDynamicData(
        ResourceId tableId,
        bytes32[] memory keyTuple,
        uint8 dynamicFieldIndex,
        uint40 startWithinField,
        uint40 deleteCount,
    +   PackedCounter encodedLengths,
        bytes memory data,
    -   PackedCounter encodedLengths
      ) external;
    }
  • #1527 759514d8 Thanks @holic! - Moved the registration of store hooks and systems hooks to bitmaps with bitwise operator instead of a struct.

    - import { StoreHookLib } from "@latticexyz/src/StoreHook.sol";
    + import {
    +   BEFORE_SET_RECORD,
    +   BEFORE_SET_FIELD,
    +   BEFORE_DELETE_RECORD
    + } from "@latticexyz/store/storeHookTypes.sol";
    
      StoreCore.registerStoreHook(
        tableId,
        subscriber,
    -   StoreHookLib.encodeBitmap({
    -     onBeforeSetRecord: true,
    -     onAfterSetRecord: false,
    -     onBeforeSetField: true,
    -     onAfterSetField: false,
    -     onBeforeDeleteRecord: true,
    -     onAfterDeleteRecord: false
    -   })
    +   BEFORE_SET_RECORD | BEFORE_SET_FIELD | BEFORE_DELETE_RECORD
      );
    - import { SystemHookLib } from "../src/SystemHook.sol";
    + import { BEFORE_CALL_SYSTEM, AFTER_CALL_SYSTEM } from "../src/systemHookTypes.sol";
    
      world.registerSystemHook(
        systemId,
        subscriber,
    -   SystemHookLib.encodeBitmap({ onBeforeCallSystem: true, onAfterCallSystem: true })
    +   BEFORE_CALL_SYSTEM | AFTER_CALL_SYSTEM
      );
    
  • #1531 d5094a24 Thanks @alvrs! - - The IStoreHook interface was changed to replace onBeforeSetField and onAfterSetField with onBeforeSpliceStaticData, onAfterSpliceStaticData, onBeforeSpliceDynamicData and onAfterSpliceDynamicData.

    This new interface matches the new StoreSpliceStaticData and StoreSpliceDynamicData events, and avoids having to read the entire field from storage when only a subset of the field was updated
    (e.g. when pushing elements to a field).

    interface IStoreHook {
    - function onBeforeSetField(
    -   bytes32 tableId,
    -   bytes32[] memory keyTuple,
    -   uint8 fieldIndex,
    -   bytes memory data,
    -   FieldLayout fieldLayout
    - ) external;
    
    - function onAfterSetField(
    -   bytes32 tableId,
    -   bytes32[] memory keyTuple,
    -   uint8 fieldIndex,
    -   bytes memory data,
    -   FieldLayout fieldLayout
    - ) external;
    
    + function onBeforeSpliceStaticData(
    +   bytes32 tableId,
    +   bytes32[] memory keyTuple,
    +   uint48 start,
    +   uint40 deleteCount,
    +   bytes memory data
    + ) external;
    
    + function onAfterSpliceStaticData(
    +   bytes32 tableId,
    +   bytes32[] memory keyTuple,
    +   uint48 start,
    +   uint40 deleteCount,
    +   bytes memory data
    + ) external;
    
    + function onBeforeSpliceDynamicData(
    +   bytes32 tableId,
    +   bytes32[] memory keyTuple,
    +   uint8 dynamicFieldIndex,
    +   uint40 startWithinField,
    +   uint40 deleteCount,
    +   bytes memory data,
    +   PackedCounter encodedLengths
    + ) external;
    
    + function onAfterSpliceDynamicData(
    +   bytes32 tableId,
    +   bytes32[] memory keyTuple,
    +   uint8 dynamicFieldIndex,
    +   uint40 startWithinField,
    +   uint40 deleteCount,
    +   bytes memory data,
    +   PackedCounter encodedLengths
    + ) external;
    }
    • All calldata parameters on the IStoreHook interface were changed to memory, since the functions are called with memory from the World.

    • IStore exposes two new functions: spliceStaticData and spliceDynamicData.

      These functions provide lower level access to the operations happening under the hood in setField, pushToField, popFromField and updateInField and simplify handling
      the new splice hooks.

      StoreCore's internal logic was simplified to use the spliceStaticData and spliceDynamicData functions instead of duplicating similar logic in different functions.

      interface IStore {
        // Splice data in the static part of the record
        function spliceStaticData(
          bytes32 tableId,
          bytes32[] calldata keyTuple,
          uint48 start,
          uint40 deleteCount,
          bytes calldata data
        ) external;
      
        // Splice data in the dynamic part of the record
        function spliceDynamicData(
          bytes32 tableId,
          bytes32[] calldata keyTuple,
          uint8 dynamicFieldIndex,
          uint40 startWithinField,
          uint40 deleteCount,
          bytes calldata data
        ) external;
      }
  • #1336 de151fec Thanks @dk1a! - - Add FieldLayout, which is a bytes32 user-type similar to Schema.

    Both FieldLayout and Schema have the same kind of data in the first 4 bytes.

    • 2 bytes for to...
Read more

@latticexyz/[email protected]

25 Sep 18:43
bdf2882
Compare
Choose a tag to compare
Pre-release

Major Changes

  • #1482 07dd6f32 Thanks @alvrs! - Renamed all occurrences of schema where it is used as "value schema" to valueSchema to clearly distinguish it from "key schema".
    The only breaking change for users is the change from schema to valueSchema in mud.config.ts.

    // mud.config.ts
    export default mudConfig({
      tables: {
        CounterTable: {
          keySchema: {},
    -     schema: {
    +     valueSchema: {
            value: "uint32",
          },
        },
      }
    }
  • #1354 331dbfdc Thanks @dk1a! - We've updated Store events to be "schemaless", meaning there is enough information in each event to only need to operate on the bytes of each record to make an update to that record without having to first decode the record by its schema. This enables new kinds of indexers and sync strategies.

    As such, we've replaced blockStorageOperations# @latticexyz/store-sync with storedBlockLogs# @latticexyz/store-sync, a stream of simplified Store event logs after they've been synced to the configured storage adapter. These logs may not reflect exactly the events that are on chain when e.g. hydrating from an indexer, but they will still allow the client to "catch up" to the on-chain state of your tables.

Patch Changes

  • #1484 6573e38e Thanks @alvrs! - Renamed all occurrences of table where it is used as "table ID" to tableId.
    This is only a breaking change for consumers who manually decode Store events, but not for consumers who use the MUD libraries.

    event StoreSetRecord(
    - bytes32 table,
    + bytes32 tableId,
      bytes32[] key,
      bytes data
    );
    
    event StoreSetField(
    - bytes32 table,
    + bytes32 tableId,
      bytes32[] key,
      uint8 fieldIndex,
      bytes data
    );
    
    event StoreDeleteRecord(
    - bytes32 table,
    + bytes32 tableId,
      bytes32[] key
    );
    
    event StoreEphemeralRecord(
    - bytes32 table,
    + bytes32 tableId,
      bytes32[] key,
      bytes data
    );
  • #1492 6e66c5b7 Thanks @alvrs! - Renamed all occurrences of key where it is used as "key tuple" to keyTuple.
    This is only a breaking change for consumers who manually decode Store events, but not for consumers who use the MUD libraries.

    event StoreSetRecord(
      bytes32 tableId,
    - bytes32[] key,
    + bytes32[] keyTuple,
      bytes data
    );
    
    event StoreSetField(
      bytes32 tableId,
    - bytes32[] key,
    + bytes32[] keyTuple,
      uint8 fieldIndex,
      bytes data
    );
    
    event StoreDeleteRecord(
      bytes32 tableId,
    - bytes32[] key,
    + bytes32[] keyTuple,
    );
    
    event StoreEphemeralRecord(
      bytes32 tableId,
    - bytes32[] key,
    + bytes32[] keyTuple,
      bytes data
    );
  • #1488 7e6e5157 Thanks @holic! - Catch errors when parsing logs to tables and storage operations, log and skip

  • #1586 22ee4470 Thanks @alvrs! - All Store and World tables now use the appropriate user-types for ResourceId, FieldLayout and Schema to avoid manual wrap/unwrap.

  • #1558 bfcb293d Thanks @alvrs! - What used to be known as ephemeral table is now called offchain table.
    The previous ephemeral tables only supported an emitEphemeral method, which emitted a StoreSetEphemeralRecord event.

    Now offchain tables support all regular table methods, except partial operations on dynamic fields (push, pop, update).
    Unlike regular tables they don't store data on-chain but emit the same events as regular tables (StoreSetRecord, StoreSpliceStaticData, StoreDeleteRecord), so their data can be indexed by offchain indexers/clients.

    - EphemeralTable.emitEphemeral(value);
    + OffchainTable.set(value);
  • #1601 1890f1a0 Thanks @alvrs! - Moved store tables to the "store" namespace (previously "mudstore") and world tables to the "world" namespace (previously root namespace).

  • #1577 af639a26 Thanks @alvrs! - Store events have been renamed for consistency and readability.
    If you're parsing Store events manually, you need to update your ABI.
    If you're using the MUD sync stack, the new events are already integrated and no further changes are necessary.

    - event StoreSetRecord(
    + event Store_SetRecord(
        ResourceId indexed tableId,
        bytes32[] keyTuple,
        bytes staticData,
        bytes32 encodedLengths,
        bytes dynamicData
      );
    - event StoreSpliceStaticData(
    + event Store_SpliceStaticData(
        ResourceId indexed tableId,
        bytes32[] keyTuple,
        uint48 start,
        uint40 deleteCount,
        bytes data
      );
    - event StoreSpliceDynamicData(
    + event Store_SpliceDynamicData(
        ResourceId indexed tableId,
        bytes32[] keyTuple,
        uint48 start,
        uint40 deleteCount,
        bytes data,
        bytes32 encodedLengths
      );
    - event StoreDeleteRecord(
    + event Store_DeleteRecord(
        ResourceId indexed tableId,
        bytes32[] keyTuple
      );
  • #1581 cea754dd Thanks @alvrs! - - The external setRecord and deleteRecord methods of IStore no longer accept a FieldLayout as input, but load it from storage instead.
    This is to prevent invalid FieldLayout values being passed, which could cause the onchain state to diverge from the indexer state.
    However, the internal StoreCore library still exposes a setRecord and deleteRecord method that allows a FieldLayout to be passed.
    This is because StoreCore can only be used internally, so the FieldLayout value can be trusted and we can save the gas for accessing storage.

    interface IStore {
      function setRecord(
        ResourceId tableId,
        bytes32[] calldata keyTuple,
        bytes calldata staticData,
        PackedCounter encodedLengths,
        bytes calldata dynamicData,
    -   FieldLayout fieldLayout
      ) external;
    
      function deleteRecord(
        ResourceId tableId,
        bytes32[] memory keyTuple,
    -   FieldLayout fieldLayout
      ) external;
    }
    • The spliceStaticData method and Store_SpliceStaticData event of IStore and StoreCore no longer include deleteCount in their signature.
      This is because when splicing static data, the data after start is always overwritten with data instead of being shifted, so deleteCount is always the length of the data to be written.

      event Store_SpliceStaticData(
        ResourceId indexed tableId,
        bytes32[] keyTuple,
        uint48 start,
      - uint40 deleteCount,
        bytes data
      );
      
      interface IStore {
        function spliceStaticData(
          ResourceId tableId,
          bytes32[] calldata keyTuple,
          uint48 start,
      -   uint40 deleteCount,
          bytes calldata data
        ) external;
      }
    • The updateInField method has been removed from IStore, as it's almost identical to the more general spliceDynamicData.
      If you're manually calling updateInField, here is how to upgrade to spliceDynamicData:

      - store.updateInField(tableId, keyTuple, fieldIndex, startByteIndex, dataToSet, fieldLayout);
      + uint8 dynamicFieldIndex = fieldIndex - fieldLayout.numStaticFields();
      + store.spliceDynamicData(tableId, keyTuple, dynamicFieldIndex, uint40(startByteIndex), uint40(dataToSet.length), dataToSet);
    • All other methods that are only valid for dynamic fields (pushToField, popFromField, getFieldSlice)
      have been renamed to make this more explicit (pushToDynamicField, popFromDynamicField, getDynamicFieldSlice).

      Their fieldIndex parameter has been replaced by a dynamicFieldIndex parameter, which is the index relative to the first dynamic field (i.e. dynamicFieldIndex = fieldIndex - numStaticFields).
      The FieldLayout parameter has been removed, as it was only used to calculate the dynamicFieldIndex in the method.

      interface IStore {
      - function pushToField(
      + function pushToDynamicField(
          ResourceId tableId,
          bytes32[] calldata keyTuple,
      -   uint8 fieldInd...
Read more

@latticexyz/[email protected]

25 Sep 18:43
bdf2882
Compare
Choose a tag to compare
Pre-release

Minor Changes

  • #1526 498d05e3 Thanks @holic! - You can now install and run @latticexyz/store-indexer from the npm package itself, without having to clone/build the MUD repo:

    npm install @latticexyz/store-indexer
    
    npm sqlite-indexer
    # or
    npm postgres-indexer

    or

    npx -p @latticexyz/store-indexer sqlite-indexer
    # or
    npx -p @latticexyz/store-indexer postgres-indexer

    The binary will also load the nearby .env file for easier local configuration.

    We've removed the CHAIN_ID requirement and instead require just a RPC_HTTP_URL or RPC_WS_URL or both. You can now also adjust the polling interval with POLLING_INTERVAL (defaults to 1000ms, which corresponds to MUD's default block time).

Patch Changes