mud

MUD is a framework for building ambitious onchain applications

MIT License

Downloads
318.4K
Stars
643
Committers
80

Bot releases are visible (Hide)

mud - @latticexyz/[email protected]

Published by github-actions[bot] about 1 year ago

Major Changes

  • #1544 5e723b90 Thanks @alvrs! - - ResourceSelector is replaced with ResourceId, ResourceIdLib, ResourceIdInstance, WorldResourceIdLib and WorldResourceIdInstance.

    Previously a "resource selector" was a bytes32 value with the first 16 bytes reserved for the resource's namespace, and the last 16 bytes reserved for the resource's name.
    Now a "resource ID" is a bytes32 value with the first 2 bytes reserved for the resource type, the next 14 bytes reserved for the resource's namespace, and the last 16 bytes reserved for the resource's name.

    Previously ResouceSelector was a library and the resource selector type was a plain bytes32.
    Now ResourceId is a user type, and the functionality is implemented in the ResourceIdInstance (for type) and WorldResourceIdInstance (for namespace and name) libraries.
    We split the logic into two libraries, because Store now also uses ResourceId and needs to be aware of resource types, but not of namespaces/names.

    - import { ResourceSelector } from "@latticexyz/world/src/ResourceSelector.sol";
    + import { ResourceId, ResourceIdInstance } from "@latticexyz/store/src/ResourceId.sol";
    + import { WorldResourceIdLib, WorldResourceIdInstance } from "@latticexyz/world/src/WorldResourceId.sol";
    + import { RESOURCE_SYSTEM } from "@latticexyz/world/src/worldResourceTypes.sol";
    
    - bytes32 systemId = ResourceSelector.from("namespace", "name");
    + ResourceId systemId = WorldResourceIdLib.encode(RESOURCE_SYSTEM, "namespace", "name");
    
    - using ResourceSelector for bytes32;
    + using WorldResourceIdInstance for ResourceId;
    + using ResourceIdInstance for ResourceId;
    
      systemId.getName();
      systemId.getNamespace();
    + systemId.getType();
    
    
    • All Store and World methods now use the ResourceId type for tableId, systemId, moduleId and namespaceId.
      All mentions of resourceSelector were renamed to resourceId or the more specific type (e.g. tableId, systemId)

      import { ResourceId } from "@latticexyz/store/src/ResourceId.sol";
      
      IStore {
        function setRecord(
      -   bytes32 tableId,
      +   ResourceId tableId,
          bytes32[] calldata keyTuple,
          bytes calldata staticData,
          PackedCounter encodedLengths,
          bytes calldata dynamicData,
          FieldLayout fieldLayout
        ) external;
      
        // Same for all other methods
      }
      
      import { ResourceId } from "@latticexyz/store/src/ResourceId.sol";
      
      IBaseWorld {
        function callFrom(
          address delegator,
      -   bytes32 resourceSelector,
      +   ResourceId systemId,
          bytes memory callData
        ) external payable returns (bytes memory);
      
        // Same for all other methods
      }
      

Patch Changes

mud - @latticexyz/[email protected]

Published by github-actions[bot] about 1 year ago

Major Changes

  • #1550 65c9546c Thanks @dk1a! - - Add renderWithFieldSuffix helper method to always render a field function with a suffix, and optionally render the same function without a suffix.

    • Remove methodNameSuffix from RenderField interface, because the suffix is now computed as part of renderWithFieldSuffix.
  • #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);
    
  • #1544 5e723b90 Thanks @alvrs! - - ResourceSelector is replaced with ResourceId, ResourceIdLib, ResourceIdInstance, WorldResourceIdLib and WorldResourceIdInstance.

    Previously a "resource selector" was a bytes32 value with the first 16 bytes reserved for the resource's namespace, and the last 16 bytes reserved for the resource's name.
    Now a "resource ID" is a bytes32 value with the first 2 bytes reserved for the resource type, the next 14 bytes reserved for the resource's namespace, and the last 16 bytes reserved for the resource's name.

    Previously ResouceSelector was a library and the resource selector type was a plain bytes32.
    Now ResourceId is a user type, and the functionality is implemented in the ResourceIdInstance (for type) and WorldResourceIdInstance (for namespace and name) libraries.
    We split the logic into two libraries, because Store now also uses ResourceId and needs to be aware of resource types, but not of namespaces/names.

    - import { ResourceSelector } from "@latticexyz/world/src/ResourceSelector.sol";
    + import { ResourceId, ResourceIdInstance } from "@latticexyz/store/src/ResourceId.sol";
    + import { WorldResourceIdLib, WorldResourceIdInstance } from "@latticexyz/world/src/WorldResourceId.sol";
    + import { RESOURCE_SYSTEM } from "@latticexyz/world/src/worldResourceTypes.sol";
    
    - bytes32 systemId = ResourceSelector.from("namespace", "name");
    + ResourceId systemId = WorldResourceIdLib.encode(RESOURCE_SYSTEM, "namespace", "name");
    
    - using ResourceSelector for bytes32;
    + using WorldResourceIdInstance for ResourceId;
    + using ResourceIdInstance for ResourceId;
    
      systemId.getName();
      systemId.getNamespace();
    + systemId.getType();
    
    
    • All Store and World methods now use the ResourceId type for tableId, systemId, moduleId and namespaceId.
      All mentions of resourceSelector were renamed to resourceId or the more specific type (e.g. tableId, systemId)

      import { ResourceId } from "@latticexyz/store/src/ResourceId.sol";
      
      IStore {
        function setRecord(
      -   bytes32 tableId,
      +   ResourceId tableId,
          bytes32[] calldata keyTuple,
          bytes calldata staticData,
          PackedCounter encodedLengths,
          bytes calldata dynamicData,
          FieldLayout fieldLayout
        ) external;
      
        // Same for all other methods
      }
      
      import { ResourceId } from "@latticexyz/store/src/ResourceId.sol";
      
      IBaseWorld {
        function callFrom(
          address delegator,
      -   bytes32 resourceSelector,
      +   ResourceId systemId,
          bytes memory callData
        ) external payable returns (bytes memory);
      
        // Same for all other methods
      }
      

Minor Changes

  • #1354 331dbfdc Thanks @dk1a! - readHex was moved from @latticexyz/protocol-parser to @latticexyz/common

  • #1566 44a5432a Thanks @dk1a! - - Add getRemappings to get foundry remappings as an array of [to, from] tuples.

    • Add extractUserTypes solidity parser utility to extract user-defined types.
    • Add loadAndExtractUserTypes helper to load and parse a solidity file, extracting user-defined types.
  • #1354 331dbfdc Thanks @dk1a! - spliceHex was added, which has a similar API as JavaScript's Array.prototype.splice, but for Hex strings.

    spliceHex("0x123456", 1, 1, "0x0000"); // "0x12000056"
    
  • #1473 92de5998 Thanks @holic! - Bump Solidity version to 0.8.21

  • #1513 708b49c5 Thanks @Boffee! - Generated table libraries now have a set of functions prefixed with _ that always use their own storage for read/write.
    This saves gas for use cases where the functionality to dynamically determine which Store to use for read/write is not needed, e.g. root systems in a World, or when using Store without World.

    We decided to continue to always generate a set of functions that dynamically decide which Store to use, so that the generated table libraries can still be imported by non-root systems.

    library Counter {
      // Dynamically determine which store to write to based on the context
      function set(uint32 value) internal;
    
      // Always write to own storage
      function _set(uint32 value) internal;
    
      // ... equivalent functions for all other Store methods
    }
    
  • #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 inference of whether the field is static or dynamic.

    • The getDynamicFieldSlice method no longer accepts reading outside the bounds of the dynamic field.
      This is to avoid returning invalid data, as the data of a dynamic field is not deleted when the record is deleted, but only its length is set to zero.

Patch Changes

  • #1585 0b8ce3f2 Thanks @alvrs! - Minor fix to resolving user types: solc doesn't like relative imports without ./, but is fine with relative imports from ./../, so we always append ./ to the relative path.

  • #1587 24a6cd53 Thanks @alvrs! - Changed the userTypes property to accept { filePath: string, internalType: SchemaAbiType } to enable strong type inference from the config.

  • #1598 c4f49240 Thanks @dk1a! - Table libraries now correctly handle uninitialized fixed length arrays.

  • Updated dependencies [92de5998]:

mud - @latticexyz/[email protected]

Published by github-actions[bot] about 1 year ago

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",
          },
        },
      }
    }
    
  • #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 total length of all static fields
    • 1 byte for number of static size fields
    • 1 byte for number of dynamic size fields

    But whereas Schema has SchemaType enum in each of the other 28 bytes, FieldLayout has static byte lengths in each of the other 28 bytes.

    • Replace Schema valueSchema with FieldLayout fieldLayout in Store and World contracts.

      FieldLayout is more gas-efficient because it already has lengths, and Schema has types which need to be converted to lengths.

    • Add getFieldLayout to IStore interface.

      There is no FieldLayout for keys, only for values, because key byte lengths aren't usually relevant on-chain. You can still use getKeySchema if you need key types.

    • Add fieldLayoutToHex utility to protocol-parser package.

    • Add constants.sol for constants shared between FieldLayout, Schema and PackedCounter.

  • #1575 e5d208e4 Thanks @alvrs! - The registerRootFunctionSelector function's signature was changed to accept a string functionSignature parameter instead of a bytes4 functionSelector parameter.
    This change enables the World to store the function signatures of all registered functions in a FunctionSignatures offchain table, which will allow for the automatic generation of interfaces for a given World address in the future.

    IBaseWorld {
      function registerRootFunctionSelector(
        ResourceId systemId,
    -   bytes4 worldFunctionSelector,
    +   string memory worldFunctionSignature,
        bytes4 systemFunctionSelector
      ) external returns (bytes4 worldFunctionSelector);
    }
    
  • #1574 31ffc9d5 Thanks @alvrs! - The registerFunctionSelector function now accepts a single functionSignature string paramemer instead of separating function name and function arguments into separate parameters.

    IBaseWorld {
      function registerFunctionSelector(
        ResourceId systemId,
    -   string memory systemFunctionName,
    -   string memory systemFunctionArguments
    +   string memory systemFunctionSignature
      ) external returns (bytes4 worldFunctionSelector);
    }
    

    This is a breaking change if you were manually registering function selectors, e.g. in a PostDeploy.s.sol script or a module.
    To upgrade, simply replace the separate systemFunctionName and systemFunctionArguments parameters with a single systemFunctionSignature parameter.

      world.registerFunctionSelector(
        systemId,
    -   systemFunctionName,
    -   systemFunctionArguments,
    +   string(abi.encodePacked(systemFunctionName, systemFunctionArguments))
      );
    
  • #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";
    
  • #1544 5e723b90 Thanks @alvrs! - - ResourceSelector is replaced with ResourceId, ResourceIdLib, ResourceIdInstance, WorldResourceIdLib and WorldResourceIdInstance.

    Previously a "resource selector" was a bytes32 value with the first 16 bytes reserved for the resource's namespace, and the last 16 bytes reserved for the resource's name.
    Now a "resource ID" is a bytes32 value with the first 2 bytes reserved for the resource type, the next 14 bytes reserved for the resource's namespace, and the last 16 bytes reserved for the resource's name.

    Previously ResouceSelector was a library and the resource selector type was a plain bytes32.
    Now ResourceId is a user type, and the functionality is implemented in the ResourceIdInstance (for type) and WorldResourceIdInstance (for namespace and name) libraries.
    We split the logic into two libraries, because Store now also uses ResourceId and needs to be aware of resource types, but not of namespaces/names.

    - import { ResourceSelector } from "@latticexyz/world/src/ResourceSelector.sol";
    + import { ResourceId, ResourceIdInstance } from "@latticexyz/store/src/ResourceId.sol";
    + import { WorldResourceIdLib, WorldResourceIdInstance } from "@latticexyz/world/src/WorldResourceId.sol";
    + import { RESOURCE_SYSTEM } from "@latticexyz/world/src/worldResourceTypes.sol";
    
    - bytes32 systemId = ResourceSelector.from("namespace", "name");
    + ResourceId systemId = WorldResourceIdLib.encode(RESOURCE_SYSTEM, "namespace", "name");
    
    - using ResourceSelector for bytes32;
    + using WorldResourceIdInstance for ResourceId;
    + using ResourceIdInstance for ResourceId;
    
      systemId.getName();
      systemId.getNamespace();
    + systemId.getType();
    
    
    • All Store and World methods now use the ResourceId type for tableId, systemId, moduleId and namespaceId.
      All mentions of resourceSelector were renamed to resourceId or the more specific type (e.g. tableId, systemId)

      import { ResourceId } from "@latticexyz/store/src/ResourceId.sol";
      
      IStore {
        function setRecord(
      -   bytes32 tableId,
      +   ResourceId tableId,
          bytes32[] calldata keyTuple,
          bytes calldata staticData,
          PackedCounter encodedLengths,
          bytes calldata dynamicData,
          FieldLayout fieldLayout
        ) external;
      
        // Same for all other methods
      }
      
      import { ResourceId } from "@latticexyz/store/src/ResourceId.sol";
      
      IBaseWorld {
        function callFrom(
          address delegator,
      -   bytes32 resourceSelector,
      +   ResourceId systemId,
          bytes memory callData
        ) external payable returns (bytes memory);
      
        // Same for all other methods
      }
      

Minor Changes

Patch Changes

  • #1490 aea67c58 Thanks @alvrs! - Include bytecode for World and Store in npm packages.

  • #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";
    
    
  • #1508 211be2a1 Thanks @Boffee! - The FieldLayout in table libraries is now generated at compile time instead of dynamically in a table library function.
    This significantly reduces gas cost in all table library functions.

  • #1503 bd9cc8ec Thanks @holic! - Refactor deploy command to break up logic into modules

  • #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);
    
  • #1521 55ab88a6 Thanks @alvrs! - StoreCore and IStore now expose specific functions for getStaticField and getDynamicField in addition to the general getField.
    Using the specific functions reduces gas overhead because more optimized logic can be executed.

    interface IStore {
      /**
       * Get a single static field from the given tableId and key tuple, with the given value field layout.
       * Note: the field value is left-aligned in the returned bytes32, the rest of the word is not zeroed out.
       * Consumers are expected to truncate the returned value as needed.
       */
      function getStaticField(
        bytes32 tableId,
        bytes32[] calldata keyTuple,
        uint8 fieldIndex,
        FieldLayout fieldLayout
      ) external view returns (bytes32);
    
      /**
       * Get a single dynamic field from the given tableId and key tuple at the given dynamic field index.
       * (Dynamic field index = field index - number of static fields)
       */
      function getDynamicField(
        bytes32 tableId,
        bytes32[] memory keyTuple,
        uint8 dynamicFieldIndex
      ) external view returns (bytes memory);
    }
    
  • #1483 83583a50 Thanks @holic! - deploy and dev-contracts CLI commands now use forge build --skip test script before deploying and run mud abi-ts to generate strong types for ABIs.

  • #1587 24a6cd53 Thanks @alvrs! - Changed the userTypes property to accept { filePath: string, internalType: SchemaAbiType } to enable strong type inference from the config.

  • #1513 708b49c5 Thanks @Boffee! - Generated table libraries now have a set of functions prefixed with _ that always use their own storage for read/write.
    This saves gas for use cases where the functionality to dynamically determine which Store to use for read/write is not needed, e.g. root systems in a World, or when using Store without World.

    We decided to continue to always generate a set of functions that dynamically decide which Store to use, so that the generated table libraries can still be imported by non-root systems.

    library Counter {
      // Dynamically determine which store to write to based on the context
      function set(uint32 value) internal;
    
      // Always write to own storage
      function _set(uint32 value) internal;
    
      // ... equivalent functions for all other Store methods
    }
    
  • #1472 c049c23f Thanks @alvrs! - - The World contract now has an initialize function, which can be called once by the creator of the World to install the core module.
    This change allows the registration of all core tables to happen in the CoreModule, so no table metadata has to be included in the World's bytecode.

    interface IBaseWorld {
      function initialize(IModule coreModule) public;
    }
    
    • The World contract now stores the original creator of the World in an immutable state variable.
      It is used internally to only allow the original creator to initialize the World in a separate transaction.

      interface IBaseWorld {
        function creator() external view returns (address);
      }
      
    • The deploy script is updated to use the World's initialize function to install the CoreModule instead of registerRootModule as before.

  • #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.

  • #1598 c4f49240 Thanks @dk1a! - Table libraries now correctly handle uninitialized fixed length arrays.

  • #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 inference of whether the field is static or dynamic.

    • The getDynamicFieldSlice method no longer accepts reading outside the bounds of the dynamic field.
      This is to avoid returning invalid data, as the data of a dynamic field is not deleted when the record is deleted, but only its length is set to zero.

  • Updated dependencies [77dce993, 748f4588, aea67c58, 07dd6f32, c07fa021, 90e4161b, 65c9546c, 331dbfdc, f9f9609e, 331dbfdc, 759514d8, d5094a24, 0b8ce3f2, de151fec, ae340b2b, e5d208e4, 211be2a1, 0f3e2e02, 1f80a0b5, d0878928, 4c7fd3eb, a0341daf, 83583a50, 5e723b90, 6573e38e, 44a5432a, 6e66c5b7, 65c9546c, f8a01a04, 44a5432a, 672d05ca, f1cd43bf, 31ffc9d5, 5e723b90, 63831a26, 331dbfdc, 92de5998, 5741d53d, 22ee4470, be313068, ac508bf1, 9ff4dd95, bfcb293d, 1890f1a0, 9b43029c, 55ab88a6, af639a26, 5e723b90, 99ab9cd6, c049c23f, 80dd6992, 24a6cd53, 708b49c5, 22ba7b67, c049c23f, 251170e1, c4f49240, cea754dd, 95c59b20]:

mud - @latticexyz/[email protected]

Published by github-actions[bot] about 1 year ago

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
    );
    
  • #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);
    
  • #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 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 inference of whether the field is static or dynamic.

    • The getDynamicFieldSlice method no longer accepts reading outside the bounds of the dynamic field.
      This is to avoid returning invalid data, as the data of a dynamic field is not deleted when the record is deleted, but only its length is set to zero.

  • Updated dependencies [65c9546c, 331dbfdc, 0b8ce3f2, 44a5432a, 331dbfdc, 92de5998, bfcb293d, 5e723b90, 24a6cd53, 708b49c5, c4f49240, cea754dd]:

mud - @latticexyz/[email protected]

Published by github-actions[bot] about 1 year ago

mud - @latticexyz/[email protected]

Published by github-actions[bot] about 1 year ago

Major Changes

  • #1458 b9e562d8 Thanks @alvrs! - The World now performs ERC165 interface checks to ensure that the StoreHook, SystemHook, System, DelegationControl and Module contracts to actually implement their respective interfaces before registering them in the World.

    The required supportsInterface methods are implemented on the respective base contracts.
    When creating one of these contracts, the recommended approach is to extend the base contract rather than the interface.

    - import { IStoreHook } from "@latticexyz/store/src/IStore.sol";
    + import { StoreHook } from "@latticexyz/store/src/StoreHook.sol";
    
    - contract MyStoreHook is IStoreHook {}
    + contract MyStoreHook is StoreHook {}
    
    - import { ISystemHook } from "@latticexyz/world/src/interfaces/ISystemHook.sol";
    + import { SystemHook } from "@latticexyz/world/src/SystemHook.sol";
    
    - contract MySystemHook is ISystemHook {}
    + contract MySystemHook is SystemHook {}
    
    - import { IDelegationControl } from "@latticexyz/world/src/interfaces/IDelegationControl.sol";
    + import { DelegationControl } from "@latticexyz/world/src/DelegationControl.sol";
    
    - contract MyDelegationControl is IDelegationControl {}
    + contract MyDelegationControl is DelegationControl {}
    
    - import { IModule } from "@latticexyz/world/src/interfaces/IModule.sol";
    + import { Module } from "@latticexyz/world/src/Module.sol";
    
    - contract MyModule is IModule {}
    + contract MyModule is Module {}
    
  • #1457 51914d65 Thanks @alvrs! - - The access control library no longer allows calls by the World contract to itself to bypass the ownership check.
    This is a breaking change for root modules that relied on this mechanism to register root tables, systems or function selectors.
    To upgrade, root modules must use delegatecall instead of a regular call to install root tables, systems or function selectors.

    - world.registerSystem(rootSystemId, rootSystemAddress);
    + address(world).delegatecall(abi.encodeCall(world.registerSystem, (rootSystemId, rootSystemAddress)));
    
    • An installRoot method was added to the IModule interface.
      This method is now called when installing a root module via world.installRootModule.
      When installing non-root modules via world.installModule, the module's install function continues to be called.
  • #1425 2ca75f9b Thanks @alvrs! - The World now maintains a balance per namespace.
    When a system is called with value, the value stored in the World contract and credited to the system's namespace.

    Previously, the World contract did not store value, but passed it on to the system contracts.
    However, as systems are expected to be stateless (reading/writing state only via the calling World) and can be registered in multiple Worlds, this could have led to exploits.

    Any address with access to a namespace can use the balance of that namespace.
    This allows all systems registered in the same namespace to work with the same balance.

    There are two new World methods to transfer balance between namespaces (transferBalanceToNamespace) or to an address (transferBalanceToAddress).

    interface IBaseWorld {
      function transferBalanceToNamespace(bytes16 fromNamespace, bytes16 toNamespace, uint256 amount) external;
    
      function transferBalanceToAddress(bytes16 fromNamespace, address toAddress, uint256 amount) external;
    }
    

Minor Changes

  • #1422 1d60930d Thanks @alvrs! - It is now possible to unregister Store hooks and System hooks.

    interface IStore {
      function unregisterStoreHook(bytes32 table, IStoreHook hookAddress) external;
      // ...
    }
    
    interface IWorld {
      function unregisterSystemHook(bytes32 resourceSelector, ISystemHook hookAddress) external;
      // ...
    }
    

Patch Changes

mud - @latticexyz/[email protected]

Published by github-actions[bot] about 1 year ago

mud - @latticexyz/[email protected]

Published by github-actions[bot] about 1 year ago

Patch Changes

mud - @latticexyz/[email protected]

Published by github-actions[bot] about 1 year ago

Patch Changes

mud - @latticexyz/[email protected]

Published by github-actions[bot] about 1 year ago

Major Changes

  • #1458 b9e562d8 Thanks @alvrs! - The World now performs ERC165 interface checks to ensure that the StoreHook, SystemHook, System, DelegationControl and Module contracts to actually implement their respective interfaces before registering them in the World.

    The required supportsInterface methods are implemented on the respective base contracts.
    When creating one of these contracts, the recommended approach is to extend the base contract rather than the interface.

    - import { IStoreHook } from "@latticexyz/store/src/IStore.sol";
    + import { StoreHook } from "@latticexyz/store/src/StoreHook.sol";
    
    - contract MyStoreHook is IStoreHook {}
    + contract MyStoreHook is StoreHook {}
    
    - import { ISystemHook } from "@latticexyz/world/src/interfaces/ISystemHook.sol";
    + import { SystemHook } from "@latticexyz/world/src/SystemHook.sol";
    
    - contract MySystemHook is ISystemHook {}
    + contract MySystemHook is SystemHook {}
    
    - import { IDelegationControl } from "@latticexyz/world/src/interfaces/IDelegationControl.sol";
    + import { DelegationControl } from "@latticexyz/world/src/DelegationControl.sol";
    
    - contract MyDelegationControl is IDelegationControl {}
    + contract MyDelegationControl is DelegationControl {}
    
    - import { IModule } from "@latticexyz/world/src/interfaces/IModule.sol";
    + import { Module } from "@latticexyz/world/src/Module.sol";
    
    - contract MyModule is IModule {}
    + contract MyModule is Module {}
    

Minor Changes

  • #1422 1d60930d Thanks @alvrs! - It is now possible to unregister Store hooks and System hooks.

    interface IStore {
      function unregisterStoreHook(bytes32 table, IStoreHook hookAddress) external;
      // ...
    }
    
    interface IWorld {
      function unregisterSystemHook(bytes32 resourceSelector, ISystemHook hookAddress) external;
      // ...
    }
    
  • #1443 5e71e1cb Thanks @holic! - Moved KeySchema, ValueSchema, SchemaToPrimitives and TableRecord types into @latticexyz/protocol-parser

Patch Changes

mud - [email protected]

Published by github-actions[bot] about 1 year ago

mud - [email protected]

Published by github-actions[bot] about 1 year ago

mud - @latticexyz/[email protected]

Published by github-actions[bot] about 1 year ago

mud - @latticexyz/[email protected]

Published by github-actions[bot] about 1 year ago

mud - @latticexyz/[email protected]

Published by github-actions[bot] about 1 year ago

Patch Changes

mud - @latticexyz/[email protected]

Published by github-actions[bot] about 1 year ago

Patch Changes

mud - @latticexyz/[email protected]

Published by github-actions[bot] about 1 year ago

Minor Changes

  • #1443 5e71e1cb Thanks @holic! - Adds decodeKey, decodeValue, encodeKey, and encodeValue helpers to decode/encode from key/value schemas. Deprecates previous methods that use a schema object with static/dynamic field arrays, originally attempting to model our on-chain behavior but ended up not very ergonomic when working with table configs.

Patch Changes

mud - @latticexyz/[email protected]

Published by github-actions[bot] about 1 year ago

Patch Changes

mud - @latticexyz/[email protected]

Published by github-actions[bot] about 1 year ago

mud - @latticexyz/[email protected]

Published by github-actions[bot] about 1 year ago