MUD is a framework for building ambitious onchain applications
MIT License
Bot releases are visible (Hide)
Published by github-actions[bot] 7 months ago
Published by github-actions[bot] 7 months ago
Published by github-actions[bot] 7 months ago
Published by github-actions[bot] 7 months ago
Published by github-actions[bot] 7 months ago
Published by github-actions[bot] 7 months ago
Published by github-actions[bot] 7 months ago
865253dba: Refactored InstalledModules
to key modules by addresses instead of pre-defined names. Previously, modules could report arbitrary names, meaning misconfigured modules could be installed under a name intended for another module.
aabd30767: Bumped Solidity version to 0.8.24.
6ca1874e0: Modules now revert with Module_AlreadyInstalled
if attempting to install more than once with the same calldata.
This is a temporary workaround for our deploy pipeline. We'll make these install steps more idempotent in the future.
251170e1e: 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.
252a1852: Migrated to new config format.
d7325e517: Added the ERC721Module
to @latticexyz/world-modules
.
This module allows the registration of ERC721
tokens in an existing World.
Important note: this module has not been audited yet, so any production use is discouraged for now.
import { PuppetModule } from "@latticexyz/world-modules/src/modules/puppet/PuppetModule.sol";
import { ERC721MetadataData } from "@latticexyz/world-modules/src/modules/erc721-puppet/tables/ERC721Metadata.sol";
import { IERC721Mintable } from "@latticexyz/world-modules/src/modules/erc721-puppet/IERC721Mintable.sol";
import { registerERC721 } from "@latticexyz/world-modules/src/modules/erc721-puppet/registerERC721.sol";
// The ERC721 module requires the Puppet module to be installed first
world.installModule(new PuppetModule(), new bytes(0));
// After the Puppet module is installed, new ERC721 tokens can be registered
IERC721Mintable token = registerERC721(world, "myERC721", ERC721MetadataData({ name: "Token", symbol: "TKN", baseURI: "" }));```
35348f831: Added the PuppetModule
to @latticexyz/world-modules
. The puppet pattern allows an external contract to be registered as an external interface for a MUD system.
This allows standards like ERC20
(that require a specific interface and events to be emitted by a unique contract) to be implemented inside a MUD World.
The puppet serves as a proxy, forwarding all calls to the implementation system (also called the "puppet master").
The "puppet master" system can emit events from the puppet contract.
import { PuppetModule } from "@latticexyz/world-modules/src/modules/puppet/PuppetModule.sol";
import { createPuppet } from "@latticexyz/world-modules/src/modules/puppet/createPuppet.sol";
// Install the puppet module
world.installModule(new PuppetModule(), new bytes(0));
// Register a new puppet for any system
// The system must implement the `CustomInterface`,
// and the caller must own the system's namespace
CustomInterface puppet = CustomInterface(createPuppet(world, <systemId>));
c4fc85041: Fixed SystemSwitch
to properly call non-root systems from root systems.
9352648b1: Since #1564 the World can no longer call itself via an external call.
This made the developer experience of calling other systems via root systems worse, since calls from root systems are executed from the context of the World.
The recommended approach is to use delegatecall
to the system if in the context of a root system, and an external call via the World if in the context of a non-root system.
To bring back the developer experience of calling systems from other sysyems without caring about the context in which the call is executed, we added the SystemSwitch
util.
- // Instead of calling the system via an external call to world...
- uint256 value = IBaseWorld(_world()).callMySystem();
+ // ...you can now use the `SystemSwitch` util.
+ // This works independent of whether used in a root system or non-root system.
+ uint256 value = abi.decode(SystemSwitch.call(abi.encodeCall(IBaseWorld.callMySystem, ()), (uint256));
Note that if you already know your system is always executed as non-root system, you can continue to use the approach of calling other systems via the IBaseWorld(world)
.
836383734: Added the ERC20Module
to @latticexyz/world-modules
.
This module allows the registration of ERC20
tokens in an existing World.
Important note: this module has not been audited yet, so any production use is discouraged for now.
import { PuppetModule } from "@latticexyz/world-modules/src/modules/puppet/PuppetModule.sol";
import { IERC20Mintable } from "@latticexyz/world-modules/src/modules/erc20-puppet/IERC20Mintable.sol";
import { registerERC20 } from "@latticexyz/world-modules/src/modules/erc20-puppet/registerERC20.sol";
// The ERC20 module requires the Puppet module to be installed first
world.installModule(new PuppetModule(), new bytes(0));
// After the Puppet module is installed, new ERC20 tokens can be registered
IERC20Mintable token = registerERC20(world, "myERC20", ERC20MetadataData({ decimals: 18, name: "Token", symbol: "TKN" }));
3042f86e: Moved key schema and value schema methods to constants in code-generated table libraries for less bytecode and less gas in register/install methods.
-console.log(SomeTable.getKeySchema());
+console.log(SomeTable._keySchema);
-console.log(SomeTable.getValueSchema());
+console.log(SomeTable._valueSchema);
fdbba6d88: Added a new delegation control called SystemboundDelegationControl
that delegates control of a specific system for some maximum number of calls. It is almost identical to CallboundDelegationControl
except the delegatee can call the system with any function and args.
7ce82b6fc: Store config now defaults storeArgument: false
for all tables. This means that table libraries, by default, will no longer include the extra functions with the _store
argument. This default was changed to clear up the confusion around using table libraries in tests, PostDeploy
scripts, etc.
If you are sure you need to manually specify a store when interacting with tables, you can still manually toggle it back on with storeArgument: true
in the table settings of your MUD config.
If you want to use table libraries in PostDeploy.s.sol
, you can add the following lines:
import { Script } from "forge-std/Script.sol";
import { console } from "forge-std/console.sol";
import { IWorld } from "../src/codegen/world/IWorld.sol";
+ import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol";
contract PostDeploy is Script {
function run(address worldAddress) external {
+ StoreSwitch.setStoreAddress(worldAddress);
+
+ SomeTable.get(someKey);
a35c05ea9: Table libraries now hardcode the bytes32
table ID value rather than computing it in Solidity. This saves a bit of gas across all storage operations.
eaa766ef7: Removed IUniqueEntitySystem
in favor of calling getUniqueEntity
via world.call
instead of the world function selector. This had a small gas improvement.
0f27afddb: World function signatures for namespaced systems have changed from {namespace}_{systemName}_{functionName}
to {namespace}__{functionName}
(double underscore, no system name). This is more ergonomic and is more consistent with namespaced resources in other parts of the codebase (e.g. MUD config types, table names in the schemaful indexer).
If you have a project using the namespace
key in your mud.config.ts
or are manually registering systems and function selectors on a namespace, you will likely need to codegen your system interfaces (pnpm build
) and update any calls to these systems through the world's namespaced function signatures.
c07fa0215: 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";
4be22ba4: ERC20 and ERC721 implementations now always register the token namespace, instead of checking if it has already been registered. This prevents issues with registering the namespace beforehand, namely that only the owner of a system can create a puppet for it.
44236041f: Moved table ID and field layout constants in code-generated table libraries from the file level into the library, for clearer access and cleaner imports.
-import { SomeTable, SomeTableTableId } from "./codegen/tables/SomeTable.sol";
+import { SomeTable } from "./codegen/tables/SomeTable.sol";
-console.log(SomeTableTableId);
+console.log(SomeTable._tableId);
-console.log(SomeTable.getFieldLayout());
+console.log(SomeTable._fieldLayout);
eb384bb0e: Added isInstalled
and requireNotInstalled
helpers to Module
base contract.
063daf80e: Previously registerSystem
and registerTable
had a side effect of registering namespaces if the system or table's namespace didn't exist yet.
This caused a possible frontrunning issue, where an attacker could detect a registerSystem
/registerTable
transaction in the mempool,
insert a registerNamespace
transaction before it, grant themselves access to the namespace, transfer ownership of the namespace to the victim,
so that the registerSystem
/registerTable
transactions still went through successfully.
To mitigate this issue, the side effect of registering a namespace in registerSystem
and registerTable
has been removed.
Calls to these functions now expect the respective namespace to exist and the caller to own the namespace, otherwise they revert.
Changes in consuming projects are only necessary if tables or systems are registered manually.
If only the MUD deployer is used to register tables and systems, no changes are necessary, as the MUD deployer has been updated accordingly.
+ world.registerNamespace(namespaceId);
world.registerSystem(systemId, system, true);
+ world.registerNamespace(namespaceId);
MyTable.register();
37c228c63: Refactored ResourceId
to use a global Solidity using
statement.
37c228c63: Refactored EIP165 usages to use the built-in interfaceId property instead of pre-defined constants.
88b1a5a19: We now expose a WorldContextConsumerLib
library with the same functionality as the WorldContextConsumer
contract, but the ability to be used inside of internal libraries.
We also renamed the WorldContextProvider
library to WorldContextProviderLib
for consistency.
590542030: TS packages now generate their respective .d.ts
type definition files for better compatibility when using MUD with moduleResolution
set to bundler
or node16
and fixes issues around missing type declarations for dependent packages.
e2d089c6d: Renamed the Module args
parameter to encodedArgs
to better reflect that it is ABI-encoded arguments.
1890f1a06: Moved store
tables to the "store"
namespace (previously "mudstore") and world
tables to the "world"
namespace (previously root namespace).
37c228c63: Refactored various Solidity files to not explicitly initialise variables to zero.
747d8d1b8: Renamed token address fields in ERC20 and ERC721 modules to tokenAddress
3e7d83d0: Renamed PackedCounter
to EncodedLengths
for consistency.
Updated dependencies [7ce82b6fc]
Updated dependencies [d8c8f66bf]
Updated dependencies [c6c13f2ea]
Updated dependencies [77dce993a]
Updated dependencies [ce97426c0]
Updated dependencies [1b86eac05]
Updated dependencies [a35c05ea9]
Updated dependencies [c9ee5e4a]
Updated dependencies [c963b46c7]
Updated dependencies [05b3e8882]
Updated dependencies [0f27afddb]
Updated dependencies [748f4588a]
Updated dependencies [865253dba]
Updated dependencies [8f49c277d]
Updated dependencies [7fa2ca183]
Updated dependencies [745485cda]
Updated dependencies [16b13ea8f]
Updated dependencies [aea67c580]
Updated dependencies [82693072]
Updated dependencies [07dd6f32c]
Updated dependencies [c07fa0215]
Updated dependencies [90e4161bb]
Updated dependencies [aabd30767]
Updated dependencies [65c9546c4]
Updated dependencies [6ca1874e0]
Updated dependencies [331dbfdcb]
Updated dependencies [d5c0682fb]
Updated dependencies [1d60930d6]
Updated dependencies [01e46d99]
Updated dependencies [430e6b29a]
Updated dependencies [f9f9609ef]
Updated dependencies [904fd7d4e]
Updated dependencies [e6c03a87a]
Updated dependencies [1077c7f53]
Updated dependencies [2c920de7]
Updated dependencies [b9e562d8f]
Updated dependencies [331dbfdcb]
Updated dependencies [44236041f]
Updated dependencies [066056154]
Updated dependencies [759514d8b]
Updated dependencies [952cd5344]
Updated dependencies [d5094a242]
Updated dependencies [3fb9ce283]
Updated dependencies [c207d35e8]
Updated dependencies [db7798be2]
Updated dependencies [bb6ada740]
Updated dependencies [35c9f33df]
Updated dependencies [3be4deecf]
Updated dependencies [a25881160]
Updated dependencies [0b8ce3f2c]
Updated dependencies [933b54b5f]
Updated dependencies [5debcca8]
Updated dependencies [c4d5eb4e4]
Updated dependencies [f8dab7334]
Updated dependencies [1a0fa7974]
Updated dependencies [f62c767e7]
Updated dependencies [d00c4a9af]
Updated dependencies [9aa5e786]
Updated dependencies [307abab3]
Updated dependencies [de151fec0]
Updated dependencies [c32a9269a]
Updated dependencies [eb384bb0e]
Updated dependencies [37c228c63]
Updated dependencies [618dd0e89]
Updated dependencies [aacffcb59]
Updated dependencies [c991c71a]
Updated dependencies [ae340b2bf]
Updated dependencies [1bf2e9087]
Updated dependencies [e5d208e40]
Updated dependencies [b38c096d]
Updated dependencies [211be2a1e]
Updated dependencies [0f3e2e02b]
Updated dependencies [1f80a0b52]
Updated dependencies [d08789282]
Updated dependencies [5c965a919]
Updated dependencies [f99e88987]
Updated dependencies [939916bcd]
Updated dependencies [e5a962bc3]
Updated dependencies [331f0d636]
Updated dependencies [f6f402896]
Updated dependencies [d5b73b126]
Updated dependencies [e34d1170]
Updated dependencies [08b422171]
Updated dependencies [b8a6158d6]
Updated dependencies [190fdd11]
Updated dependencies [37c228c63]
Updated dependencies [37c228c63]
Updated dependencies [433078c54]
Updated dependencies [db314a74]
Updated dependencies [b2d2aa715]
Updated dependencies [4c7fd3eb2]
Updated dependencies [a0341daf9]
Updated dependencies [83583a505]
Updated dependencies [5e723b90e]
Updated dependencies [6573e38e9]
Updated dependencies [51914d656]
Updated dependencies [063daf80e]
Updated dependencies [afaf2f5ff]
Updated dependencies [37c228c63]
Updated dependencies [59267655]
Updated dependencies [37c228c63]
Updated dependencies [2bfee9217]
Updated dependencies [1ca35e9a1]
Updated dependencies [44a5432ac]
Updated dependencies [6e66c5b74]
Updated dependencies [8d51a0348]
Updated dependencies [c162ad5a5]
Updated dependencies [88b1a5a19]
Updated dependencies [65c9546c4]
Updated dependencies [48909d151]
Updated dependencies [7b28d32e5]
Updated dependencies [b02f9d0e4]
Updated dependencies [2ca75f9b9]
Updated dependencies [f62c767e7]
Updated dependencies [bb91edaa0]
Updated dependencies [590542030]
Updated dependencies [1a82c278]
Updated dependencies [1b5eb0d07]
Updated dependencies [44a5432ac]
Updated dependencies [48c51b52a]
Updated dependencies [9f8b84e73]
Updated dependencies [66cc35a8c]
Updated dependencies [672d05ca1]
Updated dependencies [f1cd43bf9]
Updated dependencies [9d0f492a9]
Updated dependencies [55a05fd7a]
Updated dependencies [f03531d97]
Updated dependencies [c583f3cd0]
Updated dependencies [31ffc9d5d]
Updated dependencies [5e723b90e]
Updated dependencies [63831a264]
Updated dependencies [b8a6158d6]
Updated dependencies [6db95ce15]
Updated dependencies [8193136a9]
Updated dependencies [5d737cf2e]
Updated dependencies [d075f82f3]
Updated dependencies [331dbfdcb]
Updated dependencies [a7b30c79b]
Updated dependencies [6470fe1fd]
Updated dependencies [86766ce1]
Updated dependencies [92de59982]
Updated dependencies [5741d53d0]
Updated dependencies [aee8020a6]
Updated dependencies [22ee44700]
Updated dependencies [e2d089c6d]
Updated dependencies [ad4ac4459]
Updated dependencies [be313068b]
Updated dependencies [ac508bf18]
Updated dependencies [93390d89]
Updated dependencies [57d8965df]
Updated dependencies [18d3aea55]
Updated dependencies [7987c94d6]
Updated dependencies [bb91edaa0]
Updated dependencies [144c0d8d]
Updated dependencies [5ac4c97f4]
Updated dependencies [bfcb293d1]
Updated dependencies [3e057061d]
Updated dependencies [1890f1a06]
Updated dependencies [e48171741]
Updated dependencies [e4a6189df]
Updated dependencies [9b43029c3]
Updated dependencies [37c228c63]
Updated dependencies [55ab88a60]
Updated dependencies [c58da9ad]
Updated dependencies [37c228c63]
Updated dependencies [4e4a34150]
Updated dependencies [535229984]
Updated dependencies [af639a264]
Updated dependencies [5e723b90e]
Updated dependencies [99ab9cd6f]
Updated dependencies [be18b75b]
Updated dependencies [0c4f9fea9]
Updated dependencies [0d12db8c2]
Updated dependencies [c049c23f4]
Updated dependencies [80dd6992e]
Updated dependencies [60cfd089f]
Updated dependencies [24a6cd536]
Updated dependencies [37c228c63]
Updated dependencies [708b49c50]
Updated dependencies [d2f8e9400]
Updated dependencies [17f987209]
Updated dependencies [25086be5f]
Updated dependencies [37c228c63]
Updated dependencies [b1d41727d]
Updated dependencies [3ac68ade6]
Updated dependencies [c642ff3a0]
Updated dependencies [22ba7b675]
Updated dependencies [4c1dcd81e]
Updated dependencies [3042f86e]
Updated dependencies [c049c23f4]
Updated dependencies [5e71e1cb5]
Updated dependencies [6071163f7]
Updated dependencies [6c6733256]
Updated dependencies [cd5abcc3b]
Updated dependencies [d7b1c588a]
Updated dependencies [5c52bee09]
Updated dependencies [251170e1e]
Updated dependencies [8025c3505]
Updated dependencies [c4f49240d]
Updated dependencies [745485cda]
Updated dependencies [95f64c85]
Updated dependencies [37c228c63]
Updated dependencies [3e7d83d0]
Updated dependencies [5df1f31bc]
Updated dependencies [29c3f5087]
Updated dependencies [cea754dde]
Updated dependencies [331f0d636]
Updated dependencies [95c59b203]
Updated dependencies [cc2c8da00]
Updated dependencies [252a1852]
Updated dependencies [103f635eb]
Published by github-actions[bot] 7 months ago
77dce993a: 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";
c9ee5e4a: Store and World configs have been rebuilt with strong types. The shape of these configs have also changed slightly for clarity, the biggest change of which is merging of keySchema
and valueSchema
into a single schema
with a separate key
for a table's primary key.
To migrate, first update the imported config method:
-import { mudConfig } from "@latticexyz/world/register";
+import { defineWorld } from "@latticexyz/world";
-export default mudConfig({
+export default defineWorld({
Note that if you are only using Store, you will need to import defineStore
from @latticexyz/store
.
Then migrate the table key by renaming keySchema
to schema
and define the table key
with each field name from your key schema:
export default defineWorld({
tables: {
Position: {
- keySchema: {
+ schema: {
player: "address",
},
valueSchema: {
x: "int32",
y: "int32",
},
+ key: ['player'],
},
},
});
Now we can merge the valueSchema
into schema
.
export default defineWorld({
tables: {
Position: {
schema: {
player: "address",
- },
- valueSchema: {
x: "int32",
y: "int32",
},
key: ['player'],
},
},
});
If you previously used the table config shorthand without the full keySchema
and valueSchema
, some of the defaults have changed. Shorthands now use an id: "bytes32"
field by default rather than key: "bytes32"
and corresponding key: ["id"]
. To keep previous behavior, you may have to manually define your schema
with the previous key
and value
fields.
export default defineWorld({
tables: {
- OwnedBy: "address",
+ OwnedBy: {
+ schema: {
+ key: "bytes32",
+ value: "address",
+ },
+ key: ["key"],
+ },
},
});
Singleton tables are defined similarly, where an empty key
rather than keySchema
is provided:
-keySchema: {}
+key: []
Offchain tables are now defined as a table type
instead an offchainOnly
boolean:
-offchainOnly: true
+type: 'offchainTable'
All codegen options have moved under codegen
:
export default defineWorld({
- codegenDirectory: "…",
+ codegen: {
+ outputDirectory: "…",
+ },
tables: {
Position: {
schema: {
player: "address",
x: "int32",
y: "int32",
},
key: ['player'],
- directory: "…",
- dataStruct: false,
+ codegen: {
+ outputDirectory: "…",
+ dataStruct: false,
+ },
},
},
});
0f27afddb: World function signatures for namespaced systems have changed from {namespace}_{systemName}_{functionName}
to {namespace}__{functionName}
(double underscore, no system name). This is more ergonomic and is more consistent with namespaced resources in other parts of the codebase (e.g. MUD config types, table names in the schemaful indexer).
If you have a project using the namespace
key in your mud.config.ts
or are manually registering systems and function selectors on a namespace, you will likely need to codegen your system interfaces (pnpm build
) and update any calls to these systems through the world's namespaced function signatures.
748f4588a: 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);
865253dba: Refactored InstalledModules
to key modules by addresses instead of pre-defined names. Previously, modules could report arbitrary names, meaning misconfigured modules could be installed under a name intended for another module.
c07fa0215: 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";
aabd30767: Bumped Solidity version to 0.8.24.
331dbfdcb: 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.
b9e562d8f: 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 {}
759514d8b: 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
);
952cd5344: All Store
methods now require the table's value schema to be passed in as an argument instead of loading it from storage.
This decreases gas cost and removes circular dependencies of the Schema table (where it was not possible to write to the Schema table before the Schema table was registered).
function setRecord(
bytes32 table,
bytes32[] calldata key,
bytes calldata data,
+ Schema valueSchema
) external;
The same diff applies to getRecord
, getField
, setField
, pushToField
, popFromField
, updateInField
, and deleteRecord
.
This change only requires changes in downstream projects if the Store
methods were accessed directly. In most cases it is fully abstracted in the generated table libraries,
so downstream projects only need to regenerate their table libraries after updating MUD.
d5094a242: - 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;
}
db7798be2: Renamed CoreModule
to InitModule
and CoreRegistrationSystem
to RegistrationSystem
.
c4d5eb4e4: - The onSetRecord
hook is split into onBeforeSetRecord
and onAfterSetRecord
and the onDeleteRecord
hook is split into onBeforeDeleteRecord
and onAfterDeleteRecord
.
The purpose of this change is to allow more fine-grained control over the point in the lifecycle at which hooks are executed.
The previous hooks were executed before modifying data, so they can be replaced with the respective onBefore
hooks.
- function onSetRecord(
+ function onBeforeSetRecord(
bytes32 table,
bytes32[] memory key,
bytes memory data,
Schema valueSchema
) public;
- function onDeleteRecord(
+ function onBeforeDeleteRecord(
bytes32 table,
bytes32[] memory key,
Schema valueSchema
) public;
It is now possible to specify which methods of a hook contract should be called when registering a hook. The purpose of this change is to save gas by avoiding to call no-op hook methods.
function registerStoreHook(
bytes32 tableId,
- IStoreHook hookAddress
+ IStoreHook hookAddress,
+ uint8 enabledHooksBitmap
) public;
function registerSystemHook(
bytes32 systemId,
- ISystemHook hookAddress
+ ISystemHook hookAddress,
+ uint8 enabledHooksBitmap
) public;
There are StoreHookLib
and SystemHookLib
with helper functions to encode the bitmap of enabled hooks.
import { StoreHookLib } from "@latticexyz/store/src/StoreHook.sol";
uint8 storeHookBitmap = StoreBookLib.encodeBitmap({
onBeforeSetRecord: true,
onAfterSetRecord: true,
onBeforeSetField: true,
onAfterSetField: true,
onBeforeDeleteRecord: true,
onAfterDeleteRecord: true
});
import { SystemHookLib } from "@latticexyz/world/src/SystemHook.sol";
uint8 systemHookBitmap = SystemHookLib.encodeBitmap({
onBeforeCallSystem: true,
onAfterCallSystem: true
});
The onSetRecord
hook call for emitEphemeralRecord
has been removed to save gas and to more clearly distinguish ephemeral tables as offchain tables.
9aa5e786: Set the protocol version to 2.0.0
for each Store and World.
de151fec0: - 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.
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
.
c32a9269a: - All World
function selectors that previously had bytes16 namespace, bytes16 name
arguments now use bytes32 resourceSelector
instead.
This includes setRecord
, setField
, pushToField
, popFromField
, updateInField
, deleteRecord
, call
, grantAccess
, revokeAccess
, registerTable
,
registerStoreHook
, registerSystemHook
, registerFunctionSelector
, registerSystem
and registerRootFunctionSelector
.
This change aligns the World
function selectors with the Store
function selectors, reduces clutter, reduces gas cost and reduces the World
's contract size.
World
's registerHook
function is removed. Use registerStoreHook
or registerSystemHook
instead.deploy
script is updated to integrate the World interface changes618dd0e89: WorldFactory
now expects a user-provided salt
when calling deployWorld(...)
(instead of the previous globally incrementing counter). This enables deterministic world addresses across different chains.
When using mud deploy
, you can provide a bytes32
hex-encoded salt using the --salt
option, otherwise it defaults to a random hex value.
ae340b2bf: Store's getRecord
has been updated to return staticData
, encodedLengths
, and dynamicData
instead of a single data
blob, to match the new behaviour of Store setter methods.
If you use codegenerated libraries, you will only need to update encode
calls.
- bytes memory data = Position.encode(x, y);
+ (bytes memory staticData, PackedCounter encodedLengths, bytes memory dynamicData) = Position.encode(x, y);
e5d208e40: 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);
}
331f0d636: The SnapSyncModule
is removed. The recommended way of loading the initial state of a MUD app is via the new store-indexer
. Loading state via contract getter functions is not recommended, as it's computationally heavy on the RPC, can't be cached, and is an easy way to shoot yourself in the foot with exploding RPC costs.
The @latticexyz/network
package was deprecated and is now removed. All consumers should upgrade to the new sync stack from @latticexyz/store-sync
.
83583a505: Store and World contract ABIs are now exported from the out
directory. You'll need to update your imports like:
- import IBaseWorldAbi from "@latticexyz/world/abi/IBaseWorld.sol/IBaseWorldAbi.json";
+ import IBaseWorldAbi from "@latticexyz/world/out/IBaseWorld.sol/IBaseWorldAbi.json";
MudTest.sol
was also moved to the World package. You can update your import like:
- import { MudTest } from "@latticexyz/store/src/MudTest.sol";
+ import { MudTest } from "@latticexyz/world/test/MudTest.t.sol";
51914d656: - 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)));
installRoot
method was added to the IModule
interface.world.installRootModule
.world.installModule
, the module's install
function continues to be called.063daf80e: Previously registerSystem
and registerTable
had a side effect of registering namespaces if the system or table's namespace didn't exist yet.
This caused a possible frontrunning issue, where an attacker could detect a registerSystem
/registerTable
transaction in the mempool,
insert a registerNamespace
transaction before it, grant themselves access to the namespace, transfer ownership of the namespace to the victim,
so that the registerSystem
/registerTable
transactions still went through successfully.
To mitigate this issue, the side effect of registering a namespace in registerSystem
and registerTable
has been removed.
Calls to these functions now expect the respective namespace to exist and the caller to own the namespace, otherwise they revert.
Changes in consuming projects are only necessary if tables or systems are registered manually.
If only the MUD deployer is used to register tables and systems, no changes are necessary, as the MUD deployer has been updated accordingly.
+ world.registerNamespace(namespaceId);
world.registerSystem(systemId, system, true);
+ world.registerNamespace(namespaceId);
MyTable.register();
afaf2f5ff: - Store
's internal schema table is now a normal table instead of using special code paths. It is renamed to Tables, and the table ID changed from mudstore:schema
to mudstore:Tables
Store
's registerSchema
and setMetadata
are combined into a single registerTable
method. This means metadata (key names, field names) is immutable and indexers can create tables with this metadata when a new table is registered on-chain.
- function registerSchema(bytes32 table, Schema schema, Schema keySchema) external;
-
- function setMetadata(bytes32 table, string calldata tableName, string[] calldata fieldNames) external;
+ function registerTable(
+ bytes32 table,
+ Schema keySchema,
+ Schema valueSchema,
+ string[] calldata keyNames,
+ string[] calldata fieldNames
+ ) external;
World
's registerTable
method is updated to match the Store
interface, setMetadata
is removed
The getSchema
method is renamed to getValueSchema
on all interfaces
- function getSchema(bytes32 table) external view returns (Schema schema);
+ function getValueSchema(bytes32 table) external view returns (Schema valueSchema);
The store-sync
and cli
packages are updated to integrate the breaking protocol changes. Downstream projects only need to manually integrate these changes if they access low level Store
or World
functions. Otherwise, a fresh deploy with the latest MUD will get you these changes.
88b1a5a19: We now expose a WorldContextConsumerLib
library with the same functionality as the WorldContextConsumer
contract, but the ability to be used inside of internal libraries.
We also renamed the WorldContextProvider
library to WorldContextProviderLib
for consistency.
2ca75f9b9: 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;
}
9d0f492a9: - The previous Call.withSender
util is replaced with WorldContextProvider
, since the usecase of appending the msg.sender
to the calldata is tightly coupled with WorldContextConsumer
(which extracts the appended context from the calldata).
The previous Call.withSender
utility reverted if the call failed and only returned the returndata on success. This is replaced with callWithContextOrRevert
/delegatecallWithContextOrRevert
-import { Call } from "@latticexyz/world/src/Call.sol";
+import { WorldContextProvider } from "@latticexyz/world/src/WorldContext.sol";
-Call.withSender({
- delegate: false,
- value: 0,
- ...
-});
+WorldContextProvider.callWithContextOrRevert({
+ value: 0,
+ ...
+});
-Call.withSender({
- delegate: true,
- value: 0,
- ...
-});
+WorldContextProvider.delegatecallWithContextOrRevert({
+ ...
+});
In addition there are utils that return a bool success
flag instead of reverting on errors. This mirrors the behavior of Solidity's low level call
/delegatecall
functions and is useful in situations where additional logic should be executed in case of a reverting external call.
library WorldContextProvider {
function callWithContext(
address target, // Address to call
bytes memory funcSelectorAndArgs, // Abi encoded function selector and arguments to pass to pass to the contract
address msgSender, // Address to append to the calldata as context for msgSender
uint256 value // Value to pass with the call
) internal returns (bool success, bytes memory data);
function delegatecallWithContext(
address target, // Address to call
bytes memory funcSelectorAndArgs, // Abi encoded function selector and arguments to pass to pass to the contract
address msgSender // Address to append to the calldata as context for msgSender
) internal returns (bool success, bytes memory data);
}
WorldContext
is renamed to WorldContextConsumer
to clarify the relationship between WorldContextProvider
(appending context to the calldata) and WorldContextConsumer
(extracting context from the calldata)
-import { WorldContext } from "@latticexyz/world/src/WorldContext.sol";
-import { WorldContextConsumer } from "@latticexyz/world/src/WorldContext.sol";
The World
contract previously had a _call
method to handle calling systems via their resource selector, performing accesss control checks and call hooks registered for the system.
library SystemCall {
/**
* Calls a system via its resource selector and perform access control checks.
* Does not revert if the call fails, but returns a `success` flag along with the returndata.
*/
function call(
address caller,
bytes32 resourceSelector,
bytes memory funcSelectorAndArgs,
uint256 value
) internal returns (bool success, bytes memory data);
/**
* Calls a system via its resource selector, perform access control checks and trigger hooks registered for the system.
* Does not revert if the call fails, but returns a `success` flag along with the returndata.
*/
function callWithHooks(
address caller,
bytes32 resourceSelector,
bytes memory funcSelectorAndArgs,
uint256 value
) internal returns (bool success, bytes memory data);
/**
* Calls a system via its resource selector, perform access control checks and trigger hooks registered for the system.
* Reverts if the call fails.
*/
function callWithHooksOrRevert(
address caller,
bytes32 resourceSelector,
bytes memory funcSelectorAndArgs,
uint256 value
) internal returns (bytes memory data);
}
System hooks now are called with the system's resource selector instead of its address. The system's address can still easily obtained within the hook via Systems.get(resourceSelector)
if necessary.
interface ISystemHook {
function onBeforeCallSystem(
address msgSender,
- address systemAddress,
+ bytes32 resourceSelector,
bytes memory funcSelectorAndArgs
) external;
function onAfterCallSystem(
address msgSender,
- address systemAddress,
+ bytes32 resourceSelector,
bytes memory funcSelectorAndArgs
) external;
}
31ffc9d5d: 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))
);
5e723b90e: All World
methods acting on namespaces as resources have been updated to use ResourceId namespaceId
as parameter instead of bytes14 namespace
.
The reason for this change is to make it clearer when a namespace is used as resource, as opposed to being part of another resource's ID.
+ import { ResourceId } from "@latticexyz/store/src/ResourceId.sol";
IBaseWorld {
- function registerNamespace(bytes14 namespace) external;
+ function registerNamespace(ResourceId namespaceId) external;
- function transferOwnership(bytes14 namespace, address newOwner) external;
+ function transferOwnership(ResourceId namespaceId, address newOwner) external;
- function transferBalanceToNamespace(bytes14 fromNamespace, bytes14 toNamespace, uint256 amount) external;
+ function transferBalanceToNamespace(ResourceId fromNamespaceId, ResourceId toNamespaceId, uint256 amount) external;
- function transferBalanceToAddress(bytes14 fromNamespace, address toAddress, uint256 amount) external;
+ function transferBalanceToAddress(ResourceId fromNamespaceId, address toAddress, uint256 amount) external;
}
92de59982: Bump Solidity version to 0.8.21
5741d53d0: - IBaseWorld
now has a batchCallFrom
method, which allows system calls via callFrom
to be executed in batch.
import { SystemCallFromData } from "@latticexyz/world/modules/core/types.sol";
interface IBaseWorld {
function batchCallFrom(SystemCallFromData[] calldata systemCalls) external returns (bytes[] memory returnDatas);
}
callBatch
method of IBaseWorld
has been renamed to batchCall
to align better with the batchCallFrom
method.interface IBaseWorld {
- function callBatch(SystemCallData[] calldata systemCalls) external returns (bytes[] memory returnDatas);
+ function batchCall(SystemCallData[] calldata systemCalls) external returns (bytes[] memory returnDatas);
}
57d8965df: - Split CoreSystem
into AccessManagementSystem
, BalanceTransferSystem
, BatchCallSystem
, CoreRegistrationSystem
CoreModule
to receive the addresses of these systems as arguments, instead of deploying themCORE_SYSTEM_ID
constant with ACCESS_MANAGEMENT_SYSTEM_ID
, BALANCE_TRANSFER_SYSTEM_ID
, BATCH_CALL_SYSTEM_ID
, CORE_REGISTRATION_SYSTEM_ID
, for each respective systemThese changes separate the initcode of CoreModule
from the bytecode of core systems, which effectively removes a limit on the total bytecode of all core systems.
1890f1a06: Moved store
tables to the "store"
namespace (previously "mudstore") and world
tables to the "world"
namespace (previously root namespace).
c642ff3a0: Namespaces are not allowed to contain double underscores ("__") anymore, as this sequence of characters is used to separate the namespace and function selector in namespaced systems.
This is to prevent signature clashes of functions in different namespaces.
(Example: If namespaces were allowed to contain this separator string, a function "function" in namespace "namespace__my" would result in the namespaced function selector "namespace__my__function",
and would clash with a function "my__function" in namespace "namespace".)
251170e1e: 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.
3e7d83d0: Renamed PackedCounter
to EncodedLengths
for consistency.
cea754dde: - 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.
252a1852: Migrated to new config format.
ce97426c0: It is now possible to upgrade systems by calling registerSystem
again with an existing system id (resource selector).
// Register a system
world.registerSystem(systemId, systemAddress, publicAccess);
// Upgrade the system by calling `registerSystem` with the
// same system id but a new system address or publicAccess flag
world.registerSystem(systemId, newSystemAddress, newPublicAccess);
7fa2ca183: Added TS helpers for calling systems dynamically via the World.
encodeSystemCall
for world.call
worldContract.write.call(encodeSystemCall({
abi: worldContract.abi,
systemId: resourceToHex({ ... }),
functionName: "registerDelegation",
args: [ ... ],
}));
encodeSystemCallFrom
for world.callFrom
worldContract.write.callFrom(encodeSystemCallFrom({
abi: worldContract.abi,
from: "0x...",
systemId: resourceToHex({ ... }),
functionName: "registerDelegation",
args: [ ... ],
}));
encodeSystemCalls
for world.batchCall
worldContract.write.batchCall(encodeSystemCalls(abi, [{
systemId: resourceToHex({ ... }),
functionName: "registerDelegation",
args: [ ... ],
}]));
encodeSystemCallsFrom
for world.batchCallFrom
worldContract.write.batchCallFrom(encodeSystemCallsFrom(abi, "0x...", [{
systemId: resourceToHex({ ... }),
functionName: "registerDelegation",
args: [ ... ],
}]));
6ca1874e0: Added a Module_AlreadyInstalled
error to IModule
.
1d60930d6: 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;
// ...
}
5debcca8: registerRootFunctionSelector
now expects a systemFunctionSignature
instead of a systemFunctionSelector
. Internally, we compute the selector from the signature. This allows us to track system function signatures that are registered at the root so we can later generate ABIs for these systems.
1f80a0b52: It is now possible for namespace owners to register a fallback delegation control system for the namespace.
This fallback delegation control system is used to verify a delegation in IBaseWorld.callFrom
, after the user's individual and fallback delegations have been checked.
IBaseWorld {
function registerNamespaceDelegation(
ResourceId namespaceId,
ResourceId delegationControlId,
bytes memory initCallData
) external;
}
1ca35e9a1: The World
has a new callFrom
entry point which allows systems to be called on behalf of other addresses if those addresses have registered a delegation.
If there is a delegation, the call is forwarded to the system with delegator
as msgSender
.
interface IBaseWorld {
function callFrom(
address delegator,
bytes32 resourceSelector,
bytes memory funcSelectorAndArgs
) external payable virtual returns (bytes memory);
}
A delegation can be registered via the World
's registerDelegation
function.
If delegatee
is address(0)
, the delegation is considered to be a "fallback" delegation and is used in callFrom
if there is no delegation is found for the specific caller.
Otherwise the delegation is registered for the specific delegatee
.
interface IBaseWorld {
function registerDelegation(
address delegatee,
bytes32 delegationControl,
bytes memory initFuncSelectorAndArgs
) external;
}
The delegationControl
refers to the resource selector of a DelegationControl
system that must have been registered beforehand.
As part of registering the delegation, the DelegationControl
system is called with the provided initFuncSelectorAndArgs
.
This can be used to initialize data in the given DelegationControl
system.
The DelegationControl
system must implement the IDelegationControl
interface:
interface IDelegationControl {
function verify(address delegator, bytes32 systemId, bytes calldata funcSelectorAndArgs) external returns (bool);
}
When callFrom
is called, the World
checks if a delegation is registered for the given caller, and if so calls the delegation control's verify
function with the same same arguments as callFrom
.
If the call to verify
is successful and returns true
, the delegation is valid and the call is forwarded to the system with delegator
as msgSender
.
Note: if UNLIMITED_DELEGATION
(from @latticexyz/world/src/constants.sol
) is passed as delegationControl
, the external call to the delegation control contract is skipped and the delegation is considered valid.
For examples of DelegationControl
systems, check out the CallboundDelegationControl
or TimeboundDelegationControl
systems in the std-delegations
module.
See StandardDelegations.t.sol
for usage examples.
672d05ca1: - Moves Store events into its own IStoreEvents
interface
StoreData
abstract contract to initialize a Store and expose the Store versionIf you're using MUD out of the box, you won't have to make any changes. You will only need to update if you're using any of the base Store interfaces.
c583f3cd0: It is now possible to transfer ownership of namespaces!
// Register a new namespace
world.registerNamespace("namespace");
// It's owned by the caller of the function (address(this))
// Transfer ownership of the namespace to address(42)
world.transferOwnership("namespace", address(42));
// It's now owned by address(42)
a7b30c79b: Rename MudV2Test
to MudTest
and move from @latticexyz/std-contracts
to @latticexyz/store
.
// old import
import { MudV2Test } from "@latticexyz/std-contracts/src/test/MudV2Test.t.sol";
// new import
import { MudTest } from "@latticexyz/store/src/MudTest.sol";
Refactor StoreSwitch
to use a storage slot instead of function isStore()
to determine which contract is Store:
StoreSwitch
called isStore()
on msg.sender
to determine if msg.sender
is a Store
contract. If the call succeeded, the Store
methods were called on msg.sender
, otherwise the data was written to the own storage.StoreSwitch
instead checks for an address
in a known storage slot. If the address equals the own address, data is written to the own storage. If it is an external address, Store
methods are called on this address. If it is unset (address(0)
), store methods are called on msg.sender
.World
contracts sets its own address in StoreSwitch
, while System
contracts keep the Store address undefined, so Systems
write to their caller (World
) if they are executed via call
or directly to the World
storage if they are executed via delegatecall
.Systems
to explicitly set a Store
address to make them exclusive to that Store
andStore
argument, because the MudTest
base contract redirects reads and writes to the internal World
contract.6470fe1fd: WorldFactory
now derives a salt based on number of worlds deployed by msg.sender
, which should help with predictable world deployments across chains.
7987c94d6: Return address of the newly created World from WorldFactory.deployWorld
.
9b43029c3: Add protocol version with corresponding getter and event on deploy
world.worldVersion();
world.storeVersion(); // a World is also a Store
event HelloWorld(bytes32 indexed worldVersion);
event HelloStore(bytes32 indexed storeVersion);
25086be5f: Replaced temporary .mudtest
file in favor of WORLD_ADDRESS
environment variable when running tests with MudTest
contract
3042f86e: Moved key schema and value schema methods to constants in code-generated table libraries for less bytecode and less gas in register/install methods.
-console.log(SomeTable.getKeySchema());
+console.log(SomeTable._keySchema);
-console.log(SomeTable.getValueSchema());
+console.log(SomeTable._valueSchema);
c049c23f4: - 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.
d7b1c588a: Upgraded all packages and templates to viem v2.7.12 and abitype v1.0.0.
Some viem APIs have changed and we've updated getContract
to reflect those changes and keep it aligned with viem. It's one small code change:
const worldContract = getContract({
address: worldAddress,
abi: IWorldAbi,
- publicClient,
- walletClient,
+ client: { public: publicClient, wallet: walletClient },
});
8025c3505: We now use @latticexyz/abi-ts
to generate TS type declaration files (.d.ts
) for each ABI JSON file. This replaces our usage TypeChain everywhere.
If you previously relied on TypeChain types from @latticexyz/store
or @latticexyz/world
, you will either need to migrate to viem or abitype using ABI JSON imports or generate TypeChain types from our exported ABI JSON files.
import { getContract } from "viem";
import IStoreAbi from "@latticexyz/store/abi/IStore.sol/IStore.abi.json";
const storeContract = getContract({
abi: IStoreAbi,
...
});
await storeContract.write.setRecord(...);
95c59b203: The World
now has a callBatch
method which allows multiple system calls to be batched into a single transaction.
import { SystemCallData } from "@latticexyz/world/modules/core/types.sol";
interface IBaseWorld {
function callBatch(SystemCallData[] calldata systemCalls) external returns (bytes[] memory returnDatas);
}
7ce82b6fc: Store config now defaults storeArgument: false
for all tables. This means that table libraries, by default, will no longer include the extra functions with the _store
argument. This default was changed to clear up the confusion around using table libraries in tests, PostDeploy
scripts, etc.
If you are sure you need to manually specify a store when interacting with tables, you can still manually toggle it back on with storeArgument: true
in the table settings of your MUD config.
If you want to use table libraries in PostDeploy.s.sol
, you can add the following lines:
import { Script } from "forge-std/Script.sol";
import { console } from "forge-std/console.sol";
import { IWorld } from "../src/codegen/world/IWorld.sol";
+ import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol";
contract PostDeploy is Script {
function run(address worldAddress) external {
+ StoreSwitch.setStoreAddress(worldAddress);
+
+ SomeTable.get(someKey);
d8c8f66bf: Exclude ERC165 interface ID from custom interface ID's.
a35c05ea9: Table libraries now hardcode the bytes32
table ID value rather than computing it in Solidity. This saves a bit of gas across all storage operations.
8f49c277d: Attempting to deploy multiple systems where there are overlapping system IDs now throws an error.
745485cda: Updated WorldRegistrationSystem
to check that systems exist before registering system hooks.
aea67c580: Include bytecode for World
and Store
in npm packages.
90e4161bb: Moved the test tables out of the main config in world
and store
and into their own separate config.
430e6b29a: Register the store
namespace in the CoreModule
.
Since namespaces are a World concept, registering the Store's internal tables does not automatically register the Store's namespace, so we do this manually during initialization in the CoreModule
.
e6c03a87a: Renamed the requireNoCallback
modifier to prohibitDirectCallback
.
1077c7f53: Fixed an issue where mud.config.ts
source file was not included in the package, causing TS errors downstream.
44236041f: Moved table ID and field layout constants in code-generated table libraries from the file level into the library, for clearer access and cleaner imports.
-import { SomeTable, SomeTableTableId } from "./codegen/tables/SomeTable.sol";
+import { SomeTable } from "./codegen/tables/SomeTable.sol";
-console.log(SomeTableTableId);
+console.log(SomeTable._tableId);
-console.log(SomeTable.getFieldLayout());
+console.log(SomeTable._fieldLayout);
c207d35e8: Optimised StoreRegistrationSystem
and WorldRegistrationSystem
by fetching individual fields instead of entire records where possible.
3be4deecf: Added salt to the WorldDeployed
event.
f8dab7334: Added explicit internal
visibility to the coreSystem
variable in CoreModule
.
1a0fa7974: Fixed requireInterface
to correctly specify ERC165.
d00c4a9af: Removed ROOT_NAMESPACE_STRING
and ROOT_NAME_STRING
exports in favor of inlining these constants, to avoid reuse as they're meant for internal error messages and debugging.
eb384bb0e: Added isInstalled
and requireNotInstalled
helpers to Module
base contract.
37c228c63: Refactored various files to specify integers in a hex base instead of decimals.
211be2a1e: 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.
d08789282: Prefixed all errors with their respective library/contract for improved debugging.
e5a962bc3: World
now correctly registers the FunctionSignatures
table.
f6f402896: Added the WorldContextConsumer interface ID to supportsInterface
in the Module contract.
08b422171: Systems are expected to be always called via the central World contract.
Depending on whether it is a root or non-root system, the call is performed via delegatecall
or call
.
Since Systems are expected to be stateless and only interact with the World state, it is not necessary to prevent direct calls to the systems.
However, since the CoreSystem
is known to always be registered as a root system in the World, it is always expected to be delegatecalled,
so we made this expectation explicit by reverting if it is not delegatecalled.
37c228c63: Made the coreModule
variable in WorldFactory
immutable.
37c228c63: Removed the unnecessary extcodesize
check from the Create2
library.
433078c54: Reverse PackedCounter encoding, to optimize gas for bitshifts.
Ints are right-aligned, shifting using an index is straightforward if they are indexed right-to-left.
b2d2aa715: Added an explicit package export for mud.config
4c7fd3eb2: Remove a workaround for the internal InstalledModules
table that is not needed anymore.
a0341daf9: Renamed all funcSelectorAndArgs
arguments to callData
for clarity.
5e723b90e: The ResourceType
table is removed.
It was previously used to store the resource type for each resource ID in a World
. This is no longer necessary as the resource type is now encoded in the resource ID.
To still be able to determine whether a given resource ID exists, a ResourceIds
table has been added.
The previous ResourceType
table was part of World
and missed tables that were registered directly via StoreCore.registerTable
instead of via World.registerTable
(e.g. when a table was registered as part of a root module).
This problem is solved by the new table ResourceIds
being part of Store
.
StoreCore
's hasTable
function was removed in favor of using ResourceIds.getExists(tableId)
directly.
- import { ResourceType } from "@latticexyz/world/src/tables/ResourceType.sol";
- import { StoreCore } from "@latticexyz/store/src/StoreCore.sol";
+ import { ResourceIds } from "@latticexyz/store/src/codegen/tables/ResourceIds.sol";
- bool tableExists = StoreCore.hasTable(tableId);
+ bool tableExists = ResourceIds.getExists(tableId);
- bool systemExists = ResourceType.get(systemId) != Resource.NONE;
+ bool systemExists = ResourceIds.getExists(systemId);
37c228c63: Refactored ResourceId
to use a global Solidity using
statement.
37c228c63: Refactored EIP165 usages to use the built-in interfaceId property instead of pre-defined constants.
2bfee9217: Added a table to track the CoreModule
address the world was initialised with.
6e66c5b74: 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
);
8d51a0348: Clean up Memory.sol, make mcopy pure
48909d151: bump forge-std and ds-test dependencies
590542030: TS packages now generate their respective .d.ts
type definition files for better compatibility when using MUD with moduleResolution
set to bundler
or node16
and fixes issues around missing type declarations for dependent packages.
1a82c278: Added system signatures to the FunctionSignatures
table, so they can be used to generate system ABIs and decode system calls made via the world.
48c51b52a: RECS components are now dynamically created and inferred from your MUD config when using syncToRecs
.
To migrate existing projects after upgrading to this MUD version:
Remove contractComponents.ts
from client/src/mud
Remove components
argument from syncToRecs
Update build:mud
and dev
scripts in contracts/package.json
to remove tsgen
- "build:mud": "mud tablegen && mud worldgen && mud tsgen --configPath mud.config.ts --out ../client/src/mud",
+ "build:mud": "mud tablegen && mud worldgen",
- "dev": "pnpm mud dev-contracts --tsgenOutput ../client/src/mud",
+ "dev": "pnpm mud dev-contracts",
f1cd43bf9: Register Delegations
table in the CoreModule
86766ce1: Created an IWorldEvents
interface with HelloStore
, so all World events are defined in a single interface.
aee8020a6: Namespace balances can no longer be transferred to non-existent namespaces.
22ee44700: All Store
and World
tables now use the appropriate user-types for ResourceId
, FieldLayout
and Schema
to avoid manual wrap
/unwrap
.
e2d089c6d: Renamed the Module args
parameter to encodedArgs
to better reflect that it is ABI-encoded arguments.
be313068b: Optimized the StoreCore
hash function determining the data location to use less gas.
93390d89: Added an abstract
StoreKernel
contract, which includes all Store interfaces except for registration, and implements write methods, protocolVersion
and initializes StoreCore
. Store
extends StoreKernel
with the IStoreRegistration
interface. StoreData
is removed as a separate interface/contract. World
now extends StoreKernel
(since the registration methods are added via the InitModule
).
18d3aea55: Allow callFrom
with the own address as delegator
without requiring an explicit delegation
e48171741: Removed unused imports from various files in the store
and world
packages.
e4a6189df: Prevented invalid delegations by performing full validation regardless of whether initCallData
is empty. Added an unregisterDelegation
function which allows explicit unregistration, as opposed of passing in zero bytes into registerDelegation
.
37c228c63: Refactored various Solidity files to not explicitly initialise variables to zero.
55ab88a60: 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);
}
4e4a34150: bump to latest TS version (5.1.6)
be18b75b: IWorldKernel
now inherits IModuleErrors
so it can render the correct errors if the World reverts when delegatecalled with Module code.
0d12db8c2: Optimize Schema methods.
Return uint256
instead of uint8
in SchemaInstance numFields methods
708b49c50: 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
}
17f987209: Added a check to prevent namespaces from ending with an underscore (which could cause problems with world function signatures).
37c228c63: Refactored WorldContext
to get the world address from WorldContextConsumerLib
instead of StoreSwitch
.
22ba7b675: Simplified a couple internal constants used for bitshifting.
5c52bee09: Renamed StoreCore
's registerCoreTables
method to registerInternalTables
.
95f64c85: Renamed the functionSelector
key in the FunctionSelectors
table to worldFunctionSelector
. This clarifies that FunctionSelectors
is for world function selectors and can be used to generate the world ABI.
29c3f5087: With resource types in resource IDs, the World config no longer requires table and system names to be unique.
cc2c8da00: - Refactor tightcoder to use typescript functions instead of ejs
TightCoder
libraryisLeftAligned
and getLeftPaddingBits
common codegen helpersUpdated dependencies [7ce82b6fc]
Updated dependencies [d8c8f66bf]
Updated dependencies [c6c13f2ea]
Updated dependencies [1b86eac05]
Updated dependencies [a35c05ea9]
Updated dependencies [c9ee5e4a]
Updated dependencies [c963b46c7]
Updated dependencies [05b3e8882]
Updated dependencies [16b13ea8f]
Updated dependencies [aea67c580]
Updated dependencies [82693072]
Updated dependencies [07dd6f32c]
Updated dependencies [90e4161bb]
Updated dependencies [aabd30767]
Updated dependencies [65c9546c4]
Updated dependencies [331dbfdcb]
Updated dependencies [d5c0682fb]
Updated dependencies [1d60930d6]
Updated dependencies [01e46d99]
Updated dependencies [f9f9609ef]
Updated dependencies [904fd7d4e]
Updated dependencies [e6c03a87a]
Updated dependencies [1077c7f53]
Updated dependencies [2c920de7]
Updated dependencies [b9e562d8f]
Updated dependencies [331dbfdcb]
Updated dependencies [44236041f]
Updated dependencies [066056154]
Updated dependencies [759514d8b]
Updated dependencies [952cd5344]
Updated dependencies [d5094a242]
Updated dependencies [3fb9ce283]
Updated dependencies [bb6ada740]
Updated dependencies [35c9f33df]
Updated dependencies [a25881160]
Updated dependencies [0b8ce3f2c]
Updated dependencies [933b54b5f]
Updated dependencies [c4d5eb4e4]
Updated dependencies [f62c767e7]
Updated dependencies [9aa5e786]
Updated dependencies [307abab3]
Updated dependencies [de151fec0]
Updated dependencies [37c228c63]
Updated dependencies [aacffcb59]
Updated dependencies [c991c71a]
Updated dependencies [ae340b2bf]
Updated dependencies [1bf2e9087]
Updated dependencies [b38c096d]
Updated dependencies [211be2a1e]
Updated dependencies [0f3e2e02b]
Updated dependencies [d08789282]
Updated dependencies [5c965a919]
Updated dependencies [f99e88987]
Updated dependencies [939916bcd]
Updated dependencies [d5b73b126]
Updated dependencies [e34d1170]
Updated dependencies [b8a6158d6]
Updated dependencies [190fdd11]
Updated dependencies [433078c54]
Updated dependencies [db314a74]
Updated dependencies [b2d2aa715]
Updated dependencies [83583a505]
Updated dependencies [5e723b90e]
Updated dependencies [6573e38e9]
Updated dependencies [afaf2f5ff]
Updated dependencies [37c228c63]
Updated dependencies [59267655]
Updated dependencies [37c228c63]
Updated dependencies [44a5432ac]
Updated dependencies [6e66c5b74]
Updated dependencies [8d51a0348]
Updated dependencies [c162ad5a5]
Updated dependencies [65c9546c4]
Updated dependencies [48909d151]
Updated dependencies [7b28d32e5]
Updated dependencies [b02f9d0e4]
Updated dependencies [f62c767e7]
Updated dependencies [bb91edaa0]
Updated dependencies [590542030]
Updated dependencies [1b5eb0d07]
Updated dependencies [44a5432ac]
Updated dependencies [48c51b52a]
Updated dependencies [9f8b84e73]
Updated dependencies [66cc35a8c]
Updated dependencies [672d05ca1]
Updated dependencies [55a05fd7a]
Updated dependencies [f03531d97]
Updated dependencies [63831a264]
Updated dependencies [b8a6158d6]
Updated dependencies [6db95ce15]
Updated dependencies [8193136a9]
Updated dependencies [5d737cf2e]
Updated dependencies [d075f82f3]
Updated dependencies [331dbfdcb]
Updated dependencies [a7b30c79b]
Updated dependencies [92de59982]
Updated dependencies [22ee44700]
Updated dependencies [ad4ac4459]
Updated dependencies [be313068b]
Updated dependencies [ac508bf18]
Updated dependencies [93390d89]
Updated dependencies [bb91edaa0]
Updated dependencies [144c0d8d]
Updated dependencies [5ac4c97f4]
Updated dependencies [bfcb293d1]
Updated dependencies [3e057061d]
Updated dependencies [1890f1a06]
Updated dependencies [e48171741]
Updated dependencies [9b43029c3]
Updated dependencies [37c228c63]
Updated dependencies [55ab88a60]
Updated dependencies [c58da9ad]
Updated dependencies [37c228c63]
Updated dependencies [535229984]
Updated dependencies [af639a264]
Updated dependencies [5e723b90e]
Updated dependencies [99ab9cd6f]
Updated dependencies [0c4f9fea9]
Updated dependencies [0d12db8c2]
Updated dependencies [c049c23f4]
Updated dependencies [80dd6992e]
Updated dependencies [60cfd089f]
Updated dependencies [24a6cd536]
Updated dependencies [37c228c63]
Updated dependencies [708b49c50]
Updated dependencies [d2f8e9400]
Updated dependencies [25086be5f]
Updated dependencies [b1d41727d]
Updated dependencies [3ac68ade6]
Updated dependencies [22ba7b675]
Updated dependencies [4c1dcd81e]
Updated dependencies [3042f86e]
Updated dependencies [5e71e1cb5]
Updated dependencies [6071163f7]
Updated dependencies [6c6733256]
Updated dependencies [cd5abcc3b]
Updated dependencies [d7b1c588a]
Updated dependencies [5c52bee09]
Updated dependencies [8025c3505]
Updated dependencies [c4f49240d]
Updated dependencies [745485cda]
Updated dependencies [37c228c63]
Updated dependencies [3e7d83d0]
Updated dependencies [5df1f31bc]
Updated dependencies [cea754dde]
Updated dependencies [331f0d636]
Updated dependencies [cc2c8da00]
Updated dependencies [252a1852]
Updated dependencies [103f635eb]
Published by github-actions[bot] 7 months ago
52182f70d: Removed keccak256
and keccak256Coord
hash utils in favor of viem's keccak256
.
- import { keccak256 } from "@latticexyz/utils";
+ import { keccak256, toHex } from "viem";
- const hash = keccak256("some string");
+ const hash = keccak256(toHex("some string"));
- import { keccak256Coord } from "@latticexyz/utils";
+ import { encodeAbiParameters, keccak256, parseAbiParameters } from "viem";
const coord = { x: 1, y: 1 };
- const hash = keccak256Coord(coord);
+ const hash = keccak256(encodeAbiParameters(parseAbiParameters("int32, int32"), [coord.x, coord.y]));
.d.ts
type definition files for better compatibility when using MUD with moduleResolution
set to bundler
or node16
and fixes issues around missing type declarations for dependent packages.Published by github-actions[bot] 7 months ago
07dd6f32c: 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",
},
},
}
}
504e25dc8: lastUpdatedBlockNumber
columns in Postgres storage adapters are no longer nullable
e86fbc126: Adds store indexer service package with utils to query the indexer service.
You can run the indexer locally by checking out the MUD monorepo, installing/building everything, and running pnpm start:local
from packages/store-indexer
.
To query the indexer in the client, you can create a tRPC client with a URL pointing to the indexer service and call the available tRPC methods:
import { createIndexerClient } from "@latticexyz/store-sync/trpc-indexer";
const indexer = createIndexerClient({ url: indexerUrl });
const result = await indexer.findAll.query({
chainId: publicClient.chain.id,
address,
});
If you're using syncToRecs
, you can just pass in the indexerUrl
option as a shortcut to the above:
import { syncToRecs } from "@latticexyz/store-sync/recs";
syncToRecs({
...
indexerUrl: "https://your.indexer.service",
});
e48fb3b03: Renamed singleton chain
table to config
table for clarity.
85b94614b: The postgres indexer is now storing the logIndex
of the last update of a record to be able to return the snapshot logs in the order they were emitted onchain.
a4aff73c5: Previously, all store-sync
strategies were susceptible to a potential memory leak where the stream that fetches logs from the RPC would get ahead of the stream that stores the logs in the provided storage adapter. We saw this most often when syncing to remote Postgres servers, where inserting records was much slower than we retrieving them from the RPC. In these cases, the stream would build up a backlog of items until the machine ran out of memory.
This is now fixed by waiting for logs to be stored before fetching the next batch of logs from the RPC. To make this strategy work, we no longer return blockLogs# @latticexyz/store-sync (stream of logs fetched from RPC but before they're stored) and instead just return
storedBlockLogs# @latticexyz/store-sync (stream of logs fetched from RPC after they're stored).
1faf7f697: syncToZustand
now uses tables
argument to populate the Zustand store's tables
key, rather than the on-chain table registration events. This means we'll no longer store data into Zustand you haven't opted into receiving (e.g. other namespaces).
433078c54: Reverse PackedCounter encoding, to optimize gas for bitshifts.
Ints are right-aligned, shifting using an index is straightforward if they are indexed right-to-left.
afaf2f5ff: - Store
's internal schema table is now a normal table instead of using special code paths. It is renamed to Tables, and the table ID changed from mudstore:schema
to mudstore:Tables
Store
's registerSchema
and setMetadata
are combined into a single registerTable
method. This means metadata (key names, field names) is immutable and indexers can create tables with this metadata when a new table is registered on-chain.
- function registerSchema(bytes32 table, Schema schema, Schema keySchema) external;
-
- function setMetadata(bytes32 table, string calldata tableName, string[] calldata fieldNames) external;
+ function registerTable(
+ bytes32 table,
+ Schema keySchema,
+ Schema valueSchema,
+ string[] calldata keyNames,
+ string[] calldata fieldNames
+ ) external;
World
's registerTable
method is updated to match the Store
interface, setMetadata
is removed
The getSchema
method is renamed to getValueSchema
on all interfaces
- function getSchema(bytes32 table) external view returns (Schema schema);
+ function getValueSchema(bytes32 table) external view returns (Schema valueSchema);
The store-sync
and cli
packages are updated to integrate the breaking protocol changes. Downstream projects only need to manually integrate these changes if they access low level Store
or World
functions. Otherwise, a fresh deploy with the latest MUD will get you these changes.
48c51b52a: RECS components are now dynamically created and inferred from your MUD config when using syncToRecs
.
To migrate existing projects after upgrading to this MUD version:
Remove contractComponents.ts
from client/src/mud
Remove components
argument from syncToRecs
Update build:mud
and dev
scripts in contracts/package.json
to remove tsgen
- "build:mud": "mud tablegen && mud worldgen && mud tsgen --configPath mud.config.ts --out ../client/src/mud",
+ "build:mud": "mud tablegen && mud worldgen",
- "dev": "pnpm mud dev-contracts --tsgenOutput ../client/src/mud",
+ "dev": "pnpm mud dev-contracts",
8193136a9: Added dynamicFieldIndex
to the Store_SpliceDynamicData
event. This enables indexers to store dynamic data as a blob per dynamic field without a schema lookup.
331dbfdcb: 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.
1b5eb0d07: syncToPostgres
from @latticexyz/store-sync/postgres
now uses a single table to store all records in their bytes form (staticData
, encodedLengths
, and dynamicData
), more closely mirroring onchain state and enabling more scalability and stability for automatic indexing of many worlds.
The previous behavior, where schemaful SQL tables are created and populated for each MUD table, has been moved to a separate @latticexyz/store-sync/postgres-decoded
export bundle. This approach is considered less stable and is intended to be used for analytics purposes rather than hydrating clients. Some previous metadata columns on these tables have been removed in favor of the bytes records table as the source of truth for onchain state.
This overhaul is considered breaking and we recommend starting a fresh database when syncing with either of these strategies.
adc68225: PostgreSQL sync/indexer now uses {storeAddress}
for its database schema names and {namespace}__{tableName}
for its database table names (or just {tableName}
for root namespace), to be more consistent with the rest of the MUD codebase.
For namespaced tables:
- SELECT * FROM 0xfff__some_ns.some_table
+ SELECT * FROM 0xfff.some_ns__some_table
For root tables:
- SELECT * FROM 0xfff__.some_table
+ SELECT * FROM 0xfff.some_table
SQLite sync/indexer now uses snake case for its table names and column names for easier writing of queries and to better match PostgreSQL sync/indexer naming.
- SELECT * FROM 0xfFf__someNS__someTable
+ SELECT * FROM 0xfff__some_ns__some_table
252a1852: Migrated to new config format.
7b73f44d9: Postgres storage adapter now uses snake case for decoded table names and column names. This allows for better SQL ergonomics when querying these tables.
To avoid naming conflicts for now, schemas are still case-sensitive and need to be queried with double quotes. We may change this in the future with namespace validation.
5df1f31bc: Refactored how we fetch snapshots from an indexer, preferring the new getLogs
endpoint and falling back to the previous findAll
if it isn't available. This refactor also prepares for an easier entry point for adding client caching of snapshots.
The initialState
option for various sync methods (syncToPostgres
, syncToRecs
, etc.) is now deprecated in favor of initialBlockLogs
. For now, we'll automatically convert initialState
into initialBlockLogs
, but if you want to update your code, you can do:
import { tablesWithRecordsToLogs } from "@latticexyz/store-sync";
const initialBlockLogs = {
blockNumber: initialState.blockNumber,
logs: tablesWithRecordsToLogs(initialState.tables),
};
3622e39dd: Added a followBlockTag
option to configure which block number to follow when running createStoreSync
. It defaults to latest
(current behavior), which is recommended for individual clients so that you always have the latest chain state.
Indexers now default to safe
to avoid issues with reorgs and load-balanced RPCs being out of sync. This means indexers will be slightly behind the latest block number, but clients can quickly catch up. Indexers can override this setting using FOLLOW_BLOCK_TAG
environment variable.
904fd7d4e: Add store sync package
de47d698f: Added an optional tables
option to syncToRecs
to allow you to sync from tables that may not be expressed by your MUD config. This will be useful for namespaced tables used by ERC20 and ERC721 token modules until the MUD config gains namespace support.
Here's how we use this in our example project with the KeysWithValue
module:
syncToRecs({
...
tables: {
KeysWithValue: {
namespace: "keywval",
name: "Inventory",
tableId: resourceToHex({ type: "table", namespace: "keywval", name: "Inventory" }),
keySchema: {
valueHash: { type: "bytes32" },
},
valueSchema: {
keysWithValue: { type: "bytes32[]" },
},
},
},
...
});
131c63e53: - Accept a plain viem PublicClient
(instead of requiring a Chain
to be set) in store-sync
and store-indexer
functions. These functions now fetch chain ID using publicClient.getChainId()
when no publicClient.chain.id
is present.
store-indexer
with a set of RPC URLs (RPC_HTTP_URL
and RPC_WS_URL
) instead of CHAIN_ID
.eeb15cc06: - Replace blockEventsToStorage
with blockLogsToStorage
that exposes a storeOperations
callback to perform database writes from store operations. This helps encapsulates database adapters into a single wrapper/instance of blockLogsToStorage
and allows for wrapping a block of store operations in a database transaction.
toBlock
option to groupLogsByBlockNumber
and remove blockHash
from results. This helps track the last block number for a given set of logs when used in the context of RxJS streams.997286bac: createStoreSync
now waits for idle between each chunk of logs in a block to allow for downstream render cycles to trigger. This means that hydrating logs from an indexer will no longer block until hydration completes, but rather allow for onProgress
callbacks to trigger.
4081493b8: Added a tableIds
parameter to store sync methods and indexer to allow filtering data streams by table IDs. Store sync methods automatically include all internal table IDs from Store and World.
import { syncToRecs } from "@latticexyz/store-sync/recs";
import { resourceIdToHex } from "@latticexyz/common";
syncToRecs({
...
tableIds: [resourceIdToHex(...)],
});
import { createIndexerClient } from "@latticexyz/store-sync/trpc-indexer";
import { resourceIdToHex } from "@latticexyz/common";
const client = createIndexerClient({ ... });
client.findAll({
...
tableIds: [resourceIdToHex(...)],
});
582388ba5: Export singletonEntity
as const rather than within the syncToRecs
result.
- const { singletonEntity, ... } = syncToRecs({ ... });
+ import { singletonEntity, syncToRecs } from "@latticexyz/store-sync/recs";
+ const { ... } = syncToRecs({ ... });
f6d214e3d: Added a filters
option to store sync to allow filtering client data on tables and keys. Previously, it was only possible to filter on tableIds
, but the new filter option allows for more flexible filtering by key.
If you are building a large MUD application, you can use positional keys as a way to shard data and make it possible to load only the data needed in the client for a particular section of your app. We're using this already in Sky Strife to load match-specific data into match pages without having to load data for all matches, greatly improving load time and client performance.
syncToRecs({
...
filters: [{ tableId: '0x...', key0: '0x...' }],
});
The tableIds
option is now deprecated and will be removed in the future, but is kept here for backwards compatibility.
fa7763583: Added a Zustand storage adapter and corresponding syncToZustand
method for use in vanilla and React apps. It's used much like the other sync methods, except it returns a bound store and set of typed tables.
import { syncToZustand } from "@latticexyz/store-sync/zustand";
import config from "contracts/mud.config";
const { tables, useStore, latestBlock$, storedBlockLogs$, waitForTransaction } = await syncToZustand({
config,
...
});
// in vanilla apps
const positions = useStore.getState().getRecords(tables.Position);
// in React apps
const positions = useStore((state) => state.getRecords(tables.Position));
This change will be shortly followed by an update to our templates that uses Zustand as the default client data store and sync method.
753bdce41: Store sync logic is now consolidated into a createStoreSync
function exported from @latticexyz/store-sync
. This simplifies each storage sync strategy to just a simple wrapper around the storage adapter. You can now sync to RECS with syncToRecs
or SQLite with syncToSqlite
and PostgreSQL support coming soon.
There are no breaking changes if you were just using syncToRecs
from @latticexyz/store-sync
or running the sqlite-indexer
binary from @latticexyz/store-indexer
.
69a96f109: blockLogsToStorage(sqliteStorage(...))
converts block logs to SQLite operations. You can use it like:
import { drizzle } from "drizzle-orm/better-sqlite3";
import Database from "better-sqlite3";
import { BaseSQLiteDatabase } from "drizzle-orm/sqlite-core";
import { createPublicClient } from "viem";
import { blockLogsToStorage } from "@latticexyz/store-sync";
import { sqliteStorage } from "@latticexyz/store-sync/sqlite";
const database = drizzle(new Database('store.db')) as any as BaseSQLiteDatabase<"sync", void>;
const publicClient = createPublicClient({ ... });
blockLogs$
.pipe(
concatMap(blockLogsToStorage(sqliteStorage({ database, publicClient }))),
tap(({ blockNumber, operations }) => {
console.log("stored", operations.length, "operations for block", blockNumber);
})
)
.subscribe();
7eabd06f7: Added and populated syncProgress
key in Zustand store for sync progress, like we do for RECS sync. This will let apps using syncToZustand
render a loading state while initial client hydration is in progress.
const syncProgress = useStore((state) => state.syncProgress);
if (syncProgress.step !== SyncStep.LIVE) {
return <>Loading ({Math.floor(syncProgress.percentage)}%)</>;
}
d7b1c588a: Upgraded all packages and templates to viem v2.7.12 and abitype v1.0.0.
Some viem APIs have changed and we've updated getContract
to reflect those changes and keep it aligned with viem. It's one small code change:
const worldContract = getContract({
address: worldAddress,
abi: IWorldAbi,
- publicClient,
- walletClient,
+ client: { public: publicClient, wallet: walletClient },
});
4c1dcd81e: - Improved query performance by 10x by moving from drizzle ORM to handcrafted SQL.
trpc
for more granular control over the transport layer./api/logs
endpoint using the new query and gzip compression for 40x less data transferred over the wire./trpc/getLogs
and /trpc/findAll
endpoints.createIndexerClient
client for the new /api
indexer API exported from @latticexyz/store-sync/indexer-client
.createIndexerClient
export from @latticexyz/store-sync/trpc-indexer
is deprecated.- import { createIndexerClient } from "@latticexyz/store-sync/trpc-indexer";
+ import { createIndexerClient } from "@latticexyz/store-sync/indexer-client";
- const indexer = createIndexerClient({ url: "https://indexer.holesky.redstone.xyz/trpc" });
+ const indexer = createIndexerClient({ url: "https://indexer.holesky.redstone.xyz" });
- const snapshot = indexer.getLogs.query(options);
+ const snapshot = indexer.getLogs(options);
08d7c471f: Export postgres column type helpers from @latticexyz/store-sync
.
d5c0682fb: Updated all human-readable resource IDs to use {namespace}__{name}
for consistency with world function signatures.
0a3b9b1c9: Added explicit error logs for unexpected situations.
Previously all debug
logs were going to stderr
, which made it hard to find the unexpected errors.
Now debug
logs go to stdout
and we can add explicit stderr
logs.
6c615b608: Bumped the Postgres column size for int32
, uint32
, int64
, and uint64
types to avoid overflows
bb6ada740: Initial sync from indexer no longer blocks the promise returning from createStoreSync
, syncToRecs
, and syncToSqlite
. This should help with rendering loading screens using the SyncProgress
RECS component and avoid the long flashes of no content in templates.
By default, syncToRecs
and syncToSqlite
will start syncing (via observable subscription) immediately after called.
If your app needs to control when syncing starts, you can use the startSync: false
option and then blockStoreOperations$.subscribe()
to start the sync yourself. Just be sure to unsubscribe to avoid memory leaks.
const { blockStorageOperations$ } = syncToRecs({
...
startSync: false,
});
// start sync manually by subscribing to `blockStorageOperation# @latticexyz/store-sync
const subcription = blockStorageOperation$.subscribe();
// clean up subscription
subscription.unsubscribe();
57a526083: Adds latestBlockNumber
and lastBlockNumberProcessed
to internal SyncProgress
component
9e5baf4ff: Add RECS sync strategy and corresponding utils
import { createPublicClient, http } from 'viem';
import { syncToRecs } from '@latticexyz/store-sync';
import storeConfig from 'contracts/mud.config';
import { defineContractComponents } from './defineContractComponents';
const publicClient = createPublicClient({
chain,
transport: http(),
pollingInterval: 1000,
});
const { components, singletonEntity, latestBlock$, blockStorageOperations$, waitForTransaction } = await syncToRecs({
world,
config: storeConfig,
address: '0x...',
publicClient,
components: defineContractComponents(...),
});
712866f5f: createStoreSync
now correctly creates table registration logs from indexer records.
f99e88987: Bump viem to 1.14.0 and abitype to 0.9.8
4e445a1ab: Moved boolean array types to use array column types (instead of JSON columns) for the Postgres decoded indexer
669fa43e5: Moved numerical array types to use array column types (instead of JSON columns) for the Postgres decoded indexer
582388ba5: Add startBlock
option to syncToRecs
.
import { syncToRecs } from "@latticexyz/store-sync/recs";
import worlds from "contracts/worlds.json";
syncToRecs({
startBlock: worlds['31337'].blockNumber,
...
});
6573e38e9: 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
);
6e66c5b74: 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
);
a735e14b4: Improved syncToZustand
speed of hydrating from snapshot by only applying block logs once per block instead of once per log.
3e024fcf3: add retry attempts and more logging to waitForTransaction
590542030: TS packages now generate their respective .d.ts
type definition files for better compatibility when using MUD with moduleResolution
set to bundler
or node16
and fixes issues around missing type declarations for dependent packages.
7e6e5157b: Catch errors when parsing logs to tables and storage operations, log and skip
b8a6158d6: bump viem to 1.6.0
5d737cf2e: Updated the debug
util to pipe to stdout
and added an additional util to explicitly pipe to stderr
when needed.
22ee44700: All Store
and World
tables now use the appropriate user-types for ResourceId
, FieldLayout
and Schema
to avoid manual wrap
/unwrap
.
1327ea8c8: Fixed syncToZustand
types so that non-existent tables give an error and never
type instead of a generic Table
type.
3f5d33af: Fixes an issue with Zustand store sync where multiple updates to a record for a key in the same block did not get tracked and applied properly.
bfcb293d1: 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);
1890f1a06: Moved store
tables to the "store"
namespace (previously "mudstore") and world
tables to the "world"
namespace (previously root namespace).
5294a7d59: Improves support for internal/client-only RECS components
b8a6158d6: remove usages of isNonPendingBlock
and isNonPendingLog
(fixed with more specific viem types)
535229984: - bump to viem 1.3.0 and abitype 0.9.3
@wagmi/chains
imports to viem/chains
af639a264: 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
);
34203e4ed: Fixed invalid value when decoding records in postgres-decoded
storage adapter
6c6733256: Add tableIdToHex
and hexToTableId
pure functions and move/deprecate TableId
.
cea754dde: - 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.
d2f8e9400: Moved to new resource ID utils.
Updated dependencies [7ce82b6fc]
Updated dependencies [d8c8f66bf]
Updated dependencies [c6c13f2ea]
Updated dependencies [77dce993a]
Updated dependencies [ce97426c0]
Updated dependencies [1b86eac05]
Updated dependencies [a35c05ea9]
Updated dependencies [c9ee5e4a]
Updated dependencies [c963b46c7]
Updated dependencies [05b3e8882]
Updated dependencies [0f27afddb]
Updated dependencies [748f4588a]
Updated dependencies [865253dba]
Updated dependencies [8f49c277d]
Updated dependencies [7fa2ca183]
Updated dependencies [ce7125a1b]
Updated dependencies [745485cda]
Updated dependencies [16b13ea8f]
Updated dependencies [aea67c580]
Updated dependencies [82693072]
Updated dependencies [07dd6f32c]
Updated dependencies [c14f8bf1e]
Updated dependencies [c07fa0215]
Updated dependencies [90e4161bb]
Updated dependencies [aabd30767]
Updated dependencies [65c9546c4]
Updated dependencies [6ca1874e0]
Updated dependencies [331dbfdcb]
Updated dependencies [d5c0682fb]
Updated dependencies [1d60930d6]
Updated dependencies [01e46d99]
Updated dependencies [430e6b29a]
Updated dependencies [f9f9609ef]
Updated dependencies [904fd7d4e]
Updated dependencies [e6c03a87a]
Updated dependencies [1077c7f53]
Updated dependencies [2c920de7]
Updated dependencies [b98e51808]
Updated dependencies [b9e562d8f]
Updated dependencies [331dbfdcb]
Updated dependencies [44236041f]
Updated dependencies [066056154]
Updated dependencies [759514d8b]
Updated dependencies [952cd5344]
Updated dependencies [d5094a242]
Updated dependencies [3fb9ce283]
Updated dependencies [c207d35e8]
Updated dependencies [db7798be2]
Updated dependencies [bb6ada740]
Updated dependencies [35c9f33df]
Updated dependencies [3be4deecf]
Updated dependencies [a25881160]
Updated dependencies [0b8ce3f2c]
Updated dependencies [933b54b5f]
Updated dependencies [5debcca8]
Updated dependencies [c4d5eb4e4]
Updated dependencies [f8dab7334]
Updated dependencies [1a0fa7974]
Updated dependencies [f62c767e7]
Updated dependencies [d00c4a9af]
Updated dependencies [9aa5e786]
Updated dependencies [307abab3]
Updated dependencies [de151fec0]
Updated dependencies [c32a9269a]
Updated dependencies [eb384bb0e]
Updated dependencies [37c228c63]
Updated dependencies [618dd0e89]
Updated dependencies [aacffcb59]
Updated dependencies [c991c71a]
Updated dependencies [ae340b2bf]
Updated dependencies [1bf2e9087]
Updated dependencies [e5d208e40]
Updated dependencies [b38c096d]
Updated dependencies [211be2a1e]
Updated dependencies [0f3e2e02b]
Updated dependencies [4bb7e8cbf]
Updated dependencies [1f80a0b52]
Updated dependencies [d08789282]
Updated dependencies [5c965a919]
Updated dependencies [f99e88987]
Updated dependencies [939916bcd]
Updated dependencies [e5a962bc3]
Updated dependencies [331f0d636]
Updated dependencies [f6f402896]
Updated dependencies [d5b73b126]
Updated dependencies [e34d1170]
Updated dependencies [08b422171]
Updated dependencies [b8a6158d6]
Updated dependencies [190fdd11]
Updated dependencies [37c228c63]
Updated dependencies [37c228c63]
Updated dependencies [433078c54]
Updated dependencies [db314a74]
Updated dependencies [b2d2aa715]
Updated dependencies [4c7fd3eb2]
Updated dependencies [a0341daf9]
Updated dependencies [ca50fef81]
Updated dependencies [83583a505]
Updated dependencies [5e723b90e]
Updated dependencies [6573e38e9]
Updated dependencies [51914d656]
Updated dependencies [eeb15cc06]
Updated dependencies [063daf80e]
Updated dependencies [afaf2f5ff]
Updated dependencies [37c228c63]
Updated dependencies [59267655]
Updated dependencies [37c228c63]
Updated dependencies [72b806979]
Updated dependencies [2bfee9217]
Updated dependencies [1ca35e9a1]
Updated dependencies [44a5432ac]
Updated dependencies [6e66c5b74]
Updated dependencies [8d51a0348]
Updated dependencies [c162ad5a5]
Updated dependencies [88b1a5a19]
Updated dependencies [1e2ad78e2]
Updated dependencies [65c9546c4]
Updated dependencies [48909d151]
Updated dependencies [7b28d32e5]
Updated dependencies [f8a01a047]
Updated dependencies [b02f9d0e4]
Updated dependencies [2ca75f9b9]
Updated dependencies [f62c767e7]
Updated dependencies [bb91edaa0]
Updated dependencies [590542030]
Updated dependencies [1a82c278]
Updated dependencies [1b5eb0d07]
Updated dependencies [44a5432ac]
Updated dependencies [48c51b52a]
Updated dependencies [9f8b84e73]
Updated dependencies [66cc35a8c]
Updated dependencies [672d05ca1]
Updated dependencies [f1cd43bf9]
Updated dependencies [9d0f492a9]
Updated dependencies [55a05fd7a]
Updated dependencies [f03531d97]
Updated dependencies [c583f3cd0]
Updated dependencies [31ffc9d5d]
Updated dependencies [5e723b90e]
Updated dependencies [63831a264]
Updated dependencies [b8a6158d6]
Updated dependencies [6db95ce15]
Updated dependencies [8193136a9]
Updated dependencies [5d737cf2e]
Updated dependencies [d075f82f3]
Updated dependencies [331dbfdcb]
Updated dependencies [a7b30c79b]
Updated dependencies [6470fe1fd]
Updated dependencies [86766ce1]
Updated dependencies [92de59982]
Updated dependencies [5741d53d0]
Updated dependencies [aee8020a6]
Updated dependencies [22ee44700]
Updated dependencies [e2d089c6d]
Updated dependencies [ad4ac4459]
Updated dependencies [b8a6158d6]
Updated dependencies [be313068b]
Updated dependencies [ac508bf18]
Updated dependencies [9ff4dd955]
Updated dependencies [93390d89]
Updated dependencies [57d8965df]
Updated dependencies [18d3aea55]
Updated dependencies [7987c94d6]
Updated dependencies [bb91edaa0]
Updated dependencies [144c0d8d]
Updated dependencies [5ac4c97f4]
Updated dependencies [bfcb293d1]
Updated dependencies [3e057061d]
Updated dependencies [1890f1a06]
Updated dependencies [e48171741]
Updated dependencies [e4a6189df]
Updated dependencies [9b43029c3]
Updated dependencies [37c228c63]
Updated dependencies [55ab88a60]
Updated dependencies [c58da9ad]
Updated dependencies [37c228c63]
Updated dependencies [4e4a34150]
Updated dependencies [535229984]
Updated dependencies [af639a264]
Updated dependencies [5e723b90e]
Updated dependencies [99ab9cd6f]
Updated dependencies [be18b75b]
Updated dependencies [0c4f9fea9]
Updated dependencies [0d12db8c2]
Updated dependencies [c049c23f4]
Updated dependencies [80dd6992e]
Updated dependencies [60cfd089f]
Updated dependencies [24a6cd536]
Updated dependencies [37c228c63]
Updated dependencies [708b49c50]
Updated dependencies [d2f8e9400]
Updated dependencies [17f987209]
Updated dependencies [25086be5f]
Updated dependencies [37c228c63]
Updated dependencies [b1d41727d]
Updated dependencies [3ac68ade6]
Updated dependencies [c642ff3a0]
Updated dependencies [22ba7b675]
Updated dependencies [4c1dcd81e]
Updated dependencies [3042f86e]
Updated dependencies [c049c23f4]
Updated dependencies [5e71e1cb5]
Updated dependencies [6071163f7]
Updated dependencies [6c6733256]
Updated dependencies [cd5abcc3b]
Updated dependencies [d7b1c588a]
Updated dependencies [5c52bee09]
Updated dependencies [251170e1e]
Updated dependencies [8025c3505]
Updated dependencies [c4f49240d]
Updated dependencies [745485cda]
Updated dependencies [95f64c85]
Updated dependencies [afdba793f]
Updated dependencies [37c228c63]
Updated dependencies [3e7d83d0]
Updated dependencies [5df1f31bc]
Updated dependencies [a2f41ade9]
Updated dependencies [29c3f5087]
Updated dependencies [cea754dde]
Updated dependencies [5e71e1cb5]
Updated dependencies [331f0d636]
Updated dependencies [95c59b203]
Updated dependencies [cc2c8da00]
Updated dependencies [252a1852]
Updated dependencies [103f635eb]
Published by github-actions[bot] 7 months ago
e86fbc126: Adds store indexer service package with utils to query the indexer service.
You can run the indexer locally by checking out the MUD monorepo, installing/building everything, and running pnpm start:local
from packages/store-indexer
.
To query the indexer in the client, you can create a tRPC client with a URL pointing to the indexer service and call the available tRPC methods:
import { createIndexerClient } from "@latticexyz/store-sync/trpc-indexer";
const indexer = createIndexerClient({ url: indexerUrl });
const result = await indexer.findAll.query({
chainId: publicClient.chain.id,
address,
});
If you're using syncToRecs
, you can just pass in the indexerUrl
option as a shortcut to the above:
import { syncToRecs } from "@latticexyz/store-sync/recs";
syncToRecs({
...
indexerUrl: "https://your.indexer.service",
});
f6d214e3d: Removed tableIds
filter option in favor of the more flexible filters
option that accepts tableId
and an optional key0
and/or key1
to filter data by tables and keys.
If you were using an indexer client directly, you'll need to update your query:
await indexer.findAll.query({
chainId,
address,
- tableIds: ['0x...'],
+ filters: [{ tableId: '0x...' }],
});
b621fb977: Adds a Fastify server in front of tRPC and puts tRPC endpoints under /trpc
to make way for other top-level endpoints (e.g. tRPC panel or other API frontends like REST or gRPC).
If you're using @latticexyz/store-sync
packages with an indexer (either createIndexerClient
or indexerUrl
argument of syncToRecs
), then you'll want to update your indexer URL:
createIndexerClient({
- url: "https://indexer.dev.linfra.xyz",
+ url: "https://indexer.dev.linfra.xyz/trpc",
});
syncToRecs({
...
- indexerUrl: "https://indexer.dev.linfra.xyz",
+ indexerUrl: "https://indexer.dev.linfra.xyz/trpc",
});
85b94614b: The postgres indexer is now storing the logIndex
of the last update of a record to be able to return the snapshot logs in the order they were emitted onchain.
5ecccfe75: Separated frontend server and indexer service for Postgres indexer. Now you can run the Postgres indexer with one writer and many readers.
If you were previously using the postgres-indexer
binary, you'll now need to run both postgres-indexer
and postgres-frontend
.
For consistency, the Postgres database logs are now disabled by default. If you were using these, please let us know so we can add them back in with an environment variable flag.
adc68225: PostgreSQL sync/indexer now uses {storeAddress}
for its database schema names and {namespace}__{tableName}
for its database table names (or just {tableName}
for root namespace), to be more consistent with the rest of the MUD codebase.
For namespaced tables:
- SELECT * FROM 0xfff__some_ns.some_table
+ SELECT * FROM 0xfff.some_ns__some_table
For root tables:
- SELECT * FROM 0xfff__.some_table
+ SELECT * FROM 0xfff.some_table
SQLite sync/indexer now uses snake case for its table names and column names for easier writing of queries and to better match PostgreSQL sync/indexer naming.
- SELECT * FROM 0xfFf__someNS__someTable
+ SELECT * FROM 0xfff__some_ns__some_table
3622e39dd: Added a followBlockTag
option to configure which block number to follow when running createStoreSync
. It defaults to latest
(current behavior), which is recommended for individual clients so that you always have the latest chain state.
Indexers now default to safe
to avoid issues with reorgs and load-balanced RPCs being out of sync. This means indexers will be slightly behind the latest block number, but clients can quickly catch up. Indexers can override this setting using FOLLOW_BLOCK_TAG
environment variable.
131c63e53: - Accept a plain viem PublicClient
(instead of requiring a Chain
to be set) in store-sync
and store-indexer
functions. These functions now fetch chain ID using publicClient.getChainId()
when no publicClient.chain.id
is present.
store-indexer
with a set of RPC URLs (RPC_HTTP_URL
and RPC_WS_URL
) instead of CHAIN_ID
.4081493b8: Added a tableIds
parameter to store sync methods and indexer to allow filtering data streams by table IDs. Store sync methods automatically include all internal table IDs from Store and World.
import { syncToRecs } from "@latticexyz/store-sync/recs";
import { resourceIdToHex } from "@latticexyz/common";
syncToRecs({
...
tableIds: [resourceIdToHex(...)],
});
import { createIndexerClient } from "@latticexyz/store-sync/trpc-indexer";
import { resourceIdToHex } from "@latticexyz/common";
const client = createIndexerClient({ ... });
client.findAll({
...
tableIds: [resourceIdToHex(...)],
});
498d05e36: 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).
1b5eb0d07: The findAll
method is now considered deprecated in favor of a new getLogs
method. This is only implemented in the Postgres indexer for now, with SQLite coming soon. The new getLogs
method will be an easier and more robust data source to hydrate the client and other indexers and will allow us to add streaming updates from the indexer in the near future.
For backwards compatibility, findAll
is now implemented on top of getLogs
, with record key/value decoding done in memory at request time. This may not scale for large databases, so use wisely.
753bdce41: Store sync logic is now consolidated into a createStoreSync
function exported from @latticexyz/store-sync
. This simplifies each storage sync strategy to just a simple wrapper around the storage adapter. You can now sync to RECS with syncToRecs
or SQLite with syncToSqlite
and PostgreSQL support coming soon.
There are no breaking changes if you were just using syncToRecs
from @latticexyz/store-sync
or running the sqlite-indexer
binary from @latticexyz/store-indexer
.
e48fb3b03: When the Postgres indexer starts up, it will now attempt to detect if the database is outdated and, if so, cleans up all MUD-related schemas and tables before proceeding.
1d0f7e22b: Added /healthz
and /readyz
healthcheck endpoints for Kubernetes
d7b1c588a: Upgraded all packages and templates to viem v2.7.12 and abitype v1.0.0.
Some viem APIs have changed and we've updated getContract
to reflect those changes and keep it aligned with viem. It's one small code change:
const worldContract = getContract({
address: worldAddress,
abi: IWorldAbi,
- publicClient,
- walletClient,
+ client: { public: publicClient, wallet: walletClient },
});
5df1f31bc: Added getLogs
query support to sqlite indexer
4c1dcd81e: - Improved query performance by 10x by moving from drizzle ORM to handcrafted SQL.
trpc
for more granular control over the transport layer./api/logs
endpoint using the new query and gzip compression for 40x less data transferred over the wire./trpc/getLogs
and /trpc/findAll
endpoints.createIndexerClient
client for the new /api
indexer API exported from @latticexyz/store-sync/indexer-client
.createIndexerClient
export from @latticexyz/store-sync/trpc-indexer
is deprecated.- import { createIndexerClient } from "@latticexyz/store-sync/trpc-indexer";
+ import { createIndexerClient } from "@latticexyz/store-sync/indexer-client";
- const indexer = createIndexerClient({ url: "https://indexer.holesky.redstone.xyz/trpc" });
+ const indexer = createIndexerClient({ url: "https://indexer.holesky.redstone.xyz" });
- const snapshot = indexer.getLogs.query(options);
+ const snapshot = indexer.getLogs(options);
f61b4bc09: The /api/logs
indexer endpoint is now returning a 404
snapshot not found error when no snapshot is found for the provided filter instead of an empty 200
response.
f318f2fe7: Added STORE_ADDRESS
environment variable to index only a specific MUD Store.
504e25dc8: Records are now ordered by lastUpdatedBlockNumber
at the Postgres SQL query level
ed07018b8: Fixes postgres indexer stopping sync after it catches up to the latest block.
b00550cef: Added a script to run the decoded postgres indexer.
0a3b9b1c9: Added explicit error logs for unexpected situations.
Previously all debug
logs were going to stderr
, which made it hard to find the unexpected errors.
Now debug
logs go to stdout
and we can add explicit stderr
logs.
f99e88987: Bump viem to 1.14.0 and abitype to 0.9.8
85d16e48b: Added a Sentry middleware and SENTRY_DNS
environment variable to the postgres indexer.
c314badd1: Replaced Fastify with Koa for store-indexer frontends
301bcb75d: Improves error message when parsing env variables
b8a6158d6: bump viem to 1.6.0
392c4b88d: Disabled prepared statements for the postgres indexer, which led to issues in combination with pgBouncer
.
5d737cf2e: Updated the debug
util to pipe to stdout
and added an additional util to explicitly pipe to stderr
when needed.
5ab67e335: The error log if no data is found in /api/logs
is now stringifying the filter instead of logging [object Object]
.
735d957c6: Added a binary for the postgres-decoded
indexer.
60cfd089f: Templates and examples now use MUD's new sync packages, all built on top of viem. This greatly speeds up and stabilizes our networking code and improves types throughout.
These new sync packages come with support for our recs
package, including encodeEntity
and decodeEntity
utilities for composite keys.
If you're using store-cache
and useRow
/useRows
, you should wait to upgrade until we have a suitable replacement for those libraries. We're working on a sql.js-powered sync module that will replace store-cache
.
Migrate existing RECS apps to new sync packages
As you migrate, you may find some features replaced, removed, or not included by default. Please open an issue and let us know if we missed anything.
Add @latticexyz/store-sync
package to your app's client
package and make sure viem
is pinned to version 1.3.1
(otherwise you may get type errors)
In your supportedChains.ts
, replace foundry
chain with our new mudFoundry
chain.
- import { foundry } from "viem/chains";
- import { MUDChain, latticeTestnet } from "@latticexyz/common/chains";
+ import { MUDChain, latticeTestnet, mudFoundry } from "@latticexyz/common/chains";
- export const supportedChains: MUDChain[] = [foundry, latticeTestnet];
+ export const supportedChains: MUDChain[] = [mudFoundry, latticeTestnet];
In getNetworkConfig.ts
, remove the return type (to let TS infer it for now), remove now-unused config values, and add the viem chain
object.
- export async function getNetworkConfig(): Promise<NetworkConfig> {
+ export async function getNetworkConfig() {
const initialBlockNumber = params.has("initialBlockNumber")
? Number(params.get("initialBlockNumber"))
- : world?.blockNumber ?? -1; // -1 will attempt to find the block number from RPC
+ : world?.blockNumber ?? 0n;
+ return {
+ privateKey: getBurnerWallet().value,
+ chain,
+ worldAddress,
+ initialBlockNumber,
+ faucetServiceUrl: params.get("faucet") ?? chain.faucetUrl,
+ };
In setupNetwork.ts
, replace setupMUDV2Network
with syncToRecs
.
- import { setupMUDV2Network } from "@latticexyz/std-client";
- import { createFastTxExecutor, createFaucetService, getSnapSyncRecords } from "@latticexyz/network";
+ import { createFaucetService } from "@latticexyz/network";
+ import { createPublicClient, fallback, webSocket, http, createWalletClient, getContract, Hex, parseEther, ClientConfig } from "viem";
+ import { encodeEntity, syncToRecs } from "@latticexyz/store-sync/recs";
+ import { createBurnerAccount, createContract, transportObserver } from "@latticexyz/common";
- const result = await setupMUDV2Network({
- ...
- });
+ const clientOptions = {
+ chain: networkConfig.chain,
+ transport: transportObserver(fallback([webSocket(), http()])),
+ pollingInterval: 1000,
+ } as const satisfies ClientConfig;
+ const publicClient = createPublicClient(clientOptions);
+ const burnerAccount = createBurnerAccount(networkConfig.privateKey as Hex);
+ const burnerWalletClient = createWalletClient({
+ ...clientOptions,
+ account: burnerAccount,
+ });
+ const { components, latestBlock$, blockStorageOperations$, waitForTransaction } = await syncToRecs({
+ world,
+ config: storeConfig,
+ address: networkConfig.worldAddress as Hex,
+ publicClient,
+ components: contractComponents,
+ startBlock: BigInt(networkConfig.initialBlockNumber),
+ indexerUrl: networkConfig.indexerUrl ?? undefined,
+ });
+ const worldContract = createContract({
+ address: networkConfig.worldAddress as Hex,
+ abi: IWorld__factory.abi,
+ publicClient,
+ walletClient: burnerWalletClient,
+ });
// Request drip from faucet
- const signer = result.network.signer.get();
- if (networkConfig.faucetServiceUrl && signer) {
- const address = await signer.getAddress();
+ if (networkConfig.faucetServiceUrl) {
+ const address = burnerAccount.address;
const requestDrip = async () => {
- const balance = await signer.getBalance();
+ const balance = await publicClient.getBalance({ address });
console.info(`[Dev Faucet]: Player balance -> ${balance}`);
- const lowBalance = balance?.lte(utils.parseEther("1"));
+ const lowBalance = balance < parseEther("1");
You can remove the previous ethers worldContract
, snap sync code, and fast transaction executor.
The return of setupNetwork
is a bit different than before, so you may have to do corresponding app changes.
+ return {
+ world,
+ components,
+ playerEntity: encodeEntity({ address: "address" }, { address: burnerWalletClient.account.address }),
+ publicClient,
+ walletClient: burnerWalletClient,
+ latestBlock$,
+ blockStorageOperations$,
+ waitForTransaction,
+ worldContract,
+ };
Update createSystemCalls
with the new return type of setupNetwork
.
export function createSystemCalls(
- { worldSend, txReduced$, singletonEntity }: SetupNetworkResult,
+ { worldContract, waitForTransaction }: SetupNetworkResult,
{ Counter }: ClientComponents
) {
const increment = async () => {
- const tx = await worldSend("increment", []);
- await awaitStreamValue(txReduced$, (txHash) => txHash === tx.hash);
+ const tx = await worldContract.write.increment();
+ await waitForTransaction(tx);
return getComponentValue(Counter, singletonEntity);
};
(optional) If you still need a clock, you can create it with:
import { map, filter } from "rxjs";
import { createClock } from "@latticexyz/network";
const clock = createClock({
period: 1000,
initialTime: 0,
syncInterval: 5000,
});
world.registerDisposer(() => clock.dispose());
latestBlock$
.pipe(
map((block) => Number(block.timestamp) * 1000), // Map to timestamp in ms
filter((blockTimestamp) => blockTimestamp !== clock.lastUpdateTime), // Ignore if the clock was already refreshed with this block
filter((blockTimestamp) => blockTimestamp !== clock.currentTime), // Ignore if the current local timestamp is correct
)
.subscribe(clock.update); // Update the local clock
If you're using the previous LoadingState
component, you'll want to migrate to the new SyncProgress
:
import { SyncStep, singletonEntity } from "@latticexyz/store-sync/recs";
const syncProgress = useComponentValue(SyncProgress, singletonEntity, {
message: "Connecting",
percentage: 0,
step: SyncStep.INITIALIZE,
});
if (syncProgress.step === SyncStep.LIVE) {
// we're live!
}
b3c22a183: Added README and refactored handling of common environment variables
Updated dependencies [7ce82b6fc]
Updated dependencies [5df1f31bc]
Updated dependencies [d8c8f66bf]
Updated dependencies [c6c13f2ea]
Updated dependencies [1b86eac05]
Updated dependencies [a35c05ea9]
Updated dependencies [c9ee5e4a]
Updated dependencies [3622e39dd]
Updated dependencies [c963b46c7]
Updated dependencies [08d7c471f]
Updated dependencies [05b3e8882]
Updated dependencies [16b13ea8f]
Updated dependencies [aea67c580]
Updated dependencies [82693072]
Updated dependencies [07dd6f32c]
Updated dependencies [90e4161bb]
Updated dependencies [aabd30767]
Updated dependencies [65c9546c4]
Updated dependencies [331dbfdcb]
Updated dependencies [504e25dc8]
Updated dependencies [e86fbc126]
Updated dependencies [d5c0682fb]
Updated dependencies [1d60930d6]
Updated dependencies [01e46d99]
Updated dependencies [f9f9609ef]
Updated dependencies [904fd7d4e]
Updated dependencies [e6c03a87a]
Updated dependencies [1077c7f53]
Updated dependencies [de47d698f]
Updated dependencies [e48fb3b03]
Updated dependencies [2c920de7]
Updated dependencies [b98e51808]
Updated dependencies [0a3b9b1c9]
Updated dependencies [b9e562d8f]
Updated dependencies [331dbfdcb]
Updated dependencies [44236041f]
Updated dependencies [066056154]
Updated dependencies [759514d8b]
Updated dependencies [952cd5344]
Updated dependencies [d5094a242]
Updated dependencies [6c615b608]
Updated dependencies [3fb9ce283]
Updated dependencies [bb6ada740]
Updated dependencies [85b94614b]
Updated dependencies [35c9f33df]
Updated dependencies [a25881160]
Updated dependencies [a4aff73c5]
Updated dependencies [0b8ce3f2c]
Updated dependencies [933b54b5f]
Updated dependencies [c4d5eb4e4]
Updated dependencies [57a526083]
Updated dependencies [f62c767e7]
Updated dependencies [9e5baf4ff]
Updated dependencies [9aa5e786]
Updated dependencies [307abab3]
Updated dependencies [de151fec0]
Updated dependencies [37c228c63]
Updated dependencies [aacffcb59]
Updated dependencies [c991c71a]
Updated dependencies [1faf7f697]
Updated dependencies [ae340b2bf]
Updated dependencies [1bf2e9087]
Updated dependencies [b38c096d]
Updated dependencies [211be2a1e]
Updated dependencies [0f3e2e02b]
Updated dependencies [4bb7e8cbf]
Updated dependencies [131c63e53]
Updated dependencies [712866f5f]
Updated dependencies [d08789282]
Updated dependencies [5c965a919]
Updated dependencies [f99e88987]
Updated dependencies [939916bcd]
Updated dependencies [d5b73b126]
Updated dependencies [e34d1170]
Updated dependencies [b8a6158d6]
Updated dependencies [190fdd11]
Updated dependencies [4e445a1ab]
Updated dependencies [433078c54]
Updated dependencies [669fa43e5]
Updated dependencies [db314a74]
Updated dependencies [b2d2aa715]
Updated dependencies [ca50fef81]
Updated dependencies [83583a505]
Updated dependencies [5e723b90e]
Updated dependencies [582388ba5]
Updated dependencies [6573e38e9]
Updated dependencies [eeb15cc06]
Updated dependencies [afaf2f5ff]
Updated dependencies [37c228c63]
Updated dependencies [59267655]
Updated dependencies [37c228c63]
Updated dependencies [997286bac]
Updated dependencies [72b806979]
Updated dependencies [4081493b8]
Updated dependencies [44a5432ac]
Updated dependencies [6e66c5b74]
Updated dependencies [582388ba5]
Updated dependencies [8d51a0348]
Updated dependencies [c162ad5a5]
Updated dependencies [a735e14b4]
Updated dependencies [65c9546c4]
Updated dependencies [48909d151]
Updated dependencies [7b28d32e5]
Updated dependencies [f8a01a047]
Updated dependencies [3e024fcf3]
Updated dependencies [f62c767e7]
Updated dependencies [590542030]
Updated dependencies [1b5eb0d07]
Updated dependencies [44a5432ac]
Updated dependencies [48c51b52a]
Updated dependencies [9f8b84e73]
Updated dependencies [66cc35a8c]
Updated dependencies [672d05ca1]
Updated dependencies [55a05fd7a]
Updated dependencies [7e6e5157b]
Updated dependencies [63831a264]
Updated dependencies [b8a6158d6]
Updated dependencies [6db95ce15]
Updated dependencies [8193136a9]
Updated dependencies [5d737cf2e]
Updated dependencies [d075f82f3]
Updated dependencies [331dbfdcb]
Updated dependencies [a7b30c79b]
Updated dependencies [92de59982]
Updated dependencies [22ee44700]
Updated dependencies [1327ea8c8]
Updated dependencies [ad4ac4459]
Updated dependencies [f6d214e3d]
Updated dependencies [3f5d33af]
Updated dependencies [b8a6158d6]
Updated dependencies [be313068b]
Updated dependencies [ac508bf18]
Updated dependencies [331dbfdcb]
Updated dependencies [9ff4dd955]
Updated dependencies [93390d89]
Updated dependencies [fa7763583]
Updated dependencies [bb91edaa0]
Updated dependencies [144c0d8d]
Updated dependencies [5ac4c97f4]
Updated dependencies [bfcb293d1]
Updated dependencies [3e057061d]
Updated dependencies [1890f1a06]
Updated dependencies [e48171741]
Updated dependencies [753bdce41]
Updated dependencies [5294a7d59]
Updated dependencies [69a96f109]
Updated dependencies [9b43029c3]
Updated dependencies [37c228c63]
Updated dependencies [55ab88a60]
Updated dependencies [c58da9ad]
Updated dependencies [37c228c63]
Updated dependencies [b8a6158d6]
Updated dependencies [535229984]
Updated dependencies [af639a264]
Updated dependencies [5e723b90e]
Updated dependencies [99ab9cd6f]
Updated dependencies [0c4f9fea9]
Updated dependencies [0d12db8c2]
Updated dependencies [c049c23f4]
Updated dependencies [80dd6992e]
Updated dependencies [60cfd089f]
Updated dependencies [34203e4ed]
Updated dependencies [24a6cd536]
Updated dependencies [37c228c63]
Updated dependencies [708b49c50]
Updated dependencies [d2f8e9400]
Updated dependencies [25086be5f]
Updated dependencies [b1d41727d]
Updated dependencies [3ac68ade6]
Updated dependencies [22ba7b675]
Updated dependencies [4c1dcd81e]
Updated dependencies [3042f86e]
Updated dependencies [5e71e1cb5]
Updated dependencies [7eabd06f7]
Updated dependencies [6071163f7]
Updated dependencies [6c6733256]
Updated dependencies [cd5abcc3b]
Updated dependencies [d7b1c588a]
Updated dependencies [5c52bee09]
Updated dependencies [8025c3505]
Updated dependencies [c4f49240d]
Updated dependencies [745485cda]
Updated dependencies [37c228c63]
Updated dependencies [3e7d83d0]
Updated dependencies [5df1f31bc]
Updated dependencies [a2f41ade9]
Updated dependencies [cea754dde]
Updated dependencies [5e71e1cb5]
Updated dependencies [331f0d636]
Updated dependencies [1b5eb0d07]
Updated dependencies [d2f8e9400]
Updated dependencies [4c1dcd81e]
Updated dependencies [adc68225]
Updated dependencies [cc2c8da00]
Updated dependencies [252a1852]
Updated dependencies [7b73f44d9]
Updated dependencies [103f635eb]
Published by github-actions[bot] 7 months ago
7ce82b6fc: Store config now defaults storeArgument: false
for all tables. This means that table libraries, by default, will no longer include the extra functions with the _store
argument. This default was changed to clear up the confusion around using table libraries in tests, PostDeploy
scripts, etc.
If you are sure you need to manually specify a store when interacting with tables, you can still manually toggle it back on with storeArgument: true
in the table settings of your MUD config.
If you want to use table libraries in PostDeploy.s.sol
, you can add the following lines:
import { Script } from "forge-std/Script.sol";
import { console } from "forge-std/console.sol";
import { IWorld } from "../src/codegen/world/IWorld.sol";
+ import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol";
contract PostDeploy is Script {
function run(address worldAddress) external {
+ StoreSwitch.setStoreAddress(worldAddress);
+
+ SomeTable.get(someKey);
c9ee5e4a: Store and World configs have been rebuilt with strong types. The shape of these configs have also changed slightly for clarity, the biggest change of which is merging of keySchema
and valueSchema
into a single schema
with a separate key
for a table's primary key.
To migrate, first update the imported config method:
-import { mudConfig } from "@latticexyz/world/register";
+import { defineWorld } from "@latticexyz/world";
-export default mudConfig({
+export default defineWorld({
Note that if you are only using Store, you will need to import defineStore
from @latticexyz/store
.
Then migrate the table key by renaming keySchema
to schema
and define the table key
with each field name from your key schema:
export default defineWorld({
tables: {
Position: {
- keySchema: {
+ schema: {
player: "address",
},
valueSchema: {
x: "int32",
y: "int32",
},
+ key: ['player'],
},
},
});
Now we can merge the valueSchema
into schema
.
export default defineWorld({
tables: {
Position: {
schema: {
player: "address",
- },
- valueSchema: {
x: "int32",
y: "int32",
},
key: ['player'],
},
},
});
If you previously used the table config shorthand without the full keySchema
and valueSchema
, some of the defaults have changed. Shorthands now use an id: "bytes32"
field by default rather than key: "bytes32"
and corresponding key: ["id"]
. To keep previous behavior, you may have to manually define your schema
with the previous key
and value
fields.
export default defineWorld({
tables: {
- OwnedBy: "address",
+ OwnedBy: {
+ schema: {
+ key: "bytes32",
+ value: "address",
+ },
+ key: ["key"],
+ },
},
});
Singleton tables are defined similarly, where an empty key
rather than keySchema
is provided:
-keySchema: {}
+key: []
Offchain tables are now defined as a table type
instead an offchainOnly
boolean:
-offchainOnly: true
+type: 'offchainTable'
All codegen options have moved under codegen
:
export default defineWorld({
- codegenDirectory: "…",
+ codegen: {
+ outputDirectory: "…",
+ },
tables: {
Position: {
schema: {
player: "address",
x: "int32",
y: "int32",
},
key: ['player'],
- directory: "…",
- dataStruct: false,
+ codegen: {
+ outputDirectory: "…",
+ dataStruct: false,
+ },
},
},
});
07dd6f32c: 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",
},
},
}
}
aabd30767: Bumped Solidity version to 0.8.24.
331dbfdcb: 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.
f9f9609ef: 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;
}
b9e562d8f: 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 {}
759514d8b: 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
);
952cd5344: All Store
methods now require the table's value schema to be passed in as an argument instead of loading it from storage.
This decreases gas cost and removes circular dependencies of the Schema table (where it was not possible to write to the Schema table before the Schema table was registered).
function setRecord(
bytes32 table,
bytes32[] calldata key,
bytes calldata data,
+ Schema valueSchema
) external;
The same diff applies to getRecord
, getField
, setField
, pushToField
, popFromField
, updateInField
, and deleteRecord
.
This change only requires changes in downstream projects if the Store
methods were accessed directly. In most cases it is fully abstracted in the generated table libraries,
so downstream projects only need to regenerate their table libraries after updating MUD.
d5094a242: - 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;
}
a25881160: Remove TableId
library to simplify store
package
c4d5eb4e4: - The onSetRecord
hook is split into onBeforeSetRecord
and onAfterSetRecord
and the onDeleteRecord
hook is split into onBeforeDeleteRecord
and onAfterDeleteRecord
.
The purpose of this change is to allow more fine-grained control over the point in the lifecycle at which hooks are executed.
The previous hooks were executed before modifying data, so they can be replaced with the respective onBefore
hooks.
- function onSetRecord(
+ function onBeforeSetRecord(
bytes32 table,
bytes32[] memory key,
bytes memory data,
Schema valueSchema
) public;
- function onDeleteRecord(
+ function onBeforeDeleteRecord(
bytes32 table,
bytes32[] memory key,
Schema valueSchema
) public;
It is now possible to specify which methods of a hook contract should be called when registering a hook. The purpose of this change is to save gas by avoiding to call no-op hook methods.
function registerStoreHook(
bytes32 tableId,
- IStoreHook hookAddress
+ IStoreHook hookAddress,
+ uint8 enabledHooksBitmap
) public;
function registerSystemHook(
bytes32 systemId,
- ISystemHook hookAddress
+ ISystemHook hookAddress,
+ uint8 enabledHooksBitmap
) public;
There are StoreHookLib
and SystemHookLib
with helper functions to encode the bitmap of enabled hooks.
import { StoreHookLib } from "@latticexyz/store/src/StoreHook.sol";
uint8 storeHookBitmap = StoreBookLib.encodeBitmap({
onBeforeSetRecord: true,
onAfterSetRecord: true,
onBeforeSetField: true,
onAfterSetField: true,
onBeforeDeleteRecord: true,
onAfterDeleteRecord: true
});
import { SystemHookLib } from "@latticexyz/world/src/SystemHook.sol";
uint8 systemHookBitmap = SystemHookLib.encodeBitmap({
onBeforeCallSystem: true,
onAfterCallSystem: true
});
The onSetRecord
hook call for emitEphemeralRecord
has been removed to save gas and to more clearly distinguish ephemeral tables as offchain tables.
9aa5e786: Set the protocol version to 2.0.0
for each Store and World.
de151fec0: - 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.
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
.
ae340b2bf: Store's getRecord
has been updated to return staticData
, encodedLengths
, and dynamicData
instead of a single data
blob, to match the new behaviour of Store setter methods.
If you use codegenerated libraries, you will only need to update encode
calls.
- bytes memory data = Position.encode(x, y);
+ (bytes memory staticData, PackedCounter encodedLengths, bytes memory dynamicData) = Position.encode(x, y);
433078c54: Reverse PackedCounter encoding, to optimize gas for bitshifts.
Ints are right-aligned, shifting using an index is straightforward if they are indexed right-to-left.
83583a505: Store and World contract ABIs are now exported from the out
directory. You'll need to update your imports like:
- import IBaseWorldAbi from "@latticexyz/world/abi/IBaseWorld.sol/IBaseWorldAbi.json";
+ import IBaseWorldAbi from "@latticexyz/world/out/IBaseWorld.sol/IBaseWorldAbi.json";
MudTest.sol
was also moved to the World package. You can update your import like:
- import { MudTest } from "@latticexyz/store/src/MudTest.sol";
+ import { MudTest } from "@latticexyz/world/test/MudTest.t.sol";
afaf2f5ff: - Store
's internal schema table is now a normal table instead of using special code paths. It is renamed to Tables, and the table ID changed from mudstore:schema
to mudstore:Tables
Store
's registerSchema
and setMetadata
are combined into a single registerTable
method. This means metadata (key names, field names) is immutable and indexers can create tables with this metadata when a new table is registered on-chain.
- function registerSchema(bytes32 table, Schema schema, Schema keySchema) external;
-
- function setMetadata(bytes32 table, string calldata tableName, string[] calldata fieldNames) external;
+ function registerTable(
+ bytes32 table,
+ Schema keySchema,
+ Schema valueSchema,
+ string[] calldata keyNames,
+ string[] calldata fieldNames
+ ) external;
World
's registerTable
method is updated to match the Store
interface, setMetadata
is removed
The getSchema
method is renamed to getValueSchema
on all interfaces
- function getSchema(bytes32 table) external view returns (Schema schema);
+ function getValueSchema(bytes32 table) external view returns (Schema valueSchema);
The store-sync
and cli
packages are updated to integrate the breaking protocol changes. Downstream projects only need to manually integrate these changes if they access low level Store
or World
functions. Otherwise, a fresh deploy with the latest MUD will get you these changes.
44a5432ac: These breaking changes only affect store utilities, you aren't affected if you use @latticexyz/cli
codegen scripts.
remappings
argument to the tablegen
codegen function, so that it can read user-provided files.RenderTableOptions
change the type of imports
from RelativeImportDatum
to ImportDatum
, to allow passing absolute imports to the table renderer.solidityUserTypes
argument to several functions that need to resolve user or abi types: resolveAbiOrUserType
, importForAbiOrUserType
, getUserTypeInfo
.userTypes
config option to MUD config, which takes user types mapped to file paths from which to import them.65c9546c4: - Always render field methods with a suffix in tablegen (they used to not be rendered if field methods without a suffix were rendered).
withSuffixlessFieldMethods
to RenderTableOptions
, which indicates that field methods without a suffix should be rendered.672d05ca1: - Moves Store events into its own IStoreEvents
interface
StoreData
abstract contract to initialize a Store and expose the Store versionIf you're using MUD out of the box, you won't have to make any changes. You will only need to update if you're using any of the base Store interfaces.
8193136a9: Added dynamicFieldIndex
to the Store_SpliceDynamicData
event. This enables indexers to store dynamic data as a blob per dynamic field without a schema lookup.
92de59982: Bump Solidity version to 0.8.21
ac508bf18: 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";
bfcb293d1: 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);
1890f1a06: Moved store
tables to the "store"
namespace (previously "mudstore") and world
tables to the "world"
namespace (previously root namespace).
af639a264: 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
);
5e723b90e: - 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
}
99ab9cd6f: Store events now use an indexed
tableId
. This adds ~100 gas per write, but means we our sync stack can filter events by table.
c049c23f4: - StoreCore
's initialize
function is split into initialize
(to set the StoreSwitch
's storeAddress
) and registerCoreTables
(to register the Tables
and StoreHooks
tables).
The purpose of this is to give consumers more granular control over the setup flow.
StoreRead
contract no longer calls StoreCore.initialize
in its constructor.StoreCore
consumers are expected to call StoreCore.initialize
and StoreCore.registerCoreTable
in their own setup logic.24a6cd536: Changed the userTypes
property to accept { filePath: string, internalType: SchemaAbiType }
to enable strong type inference from the config.
5c52bee09: Renamed StoreCore
's registerCoreTables
method to registerInternalTables
.
3e7d83d0: Renamed PackedCounter
to EncodedLengths
for consistency.
cea754dde: - 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.
252a1852: Migrated to new config format.
1d60930d6: 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;
// ...
}
66cc35a8c: Create gas-report package, move gas-report cli command and GasReporter contract to it
a7b30c79b: Rename MudV2Test
to MudTest
and move from @latticexyz/std-contracts
to @latticexyz/store
.
// old import
import { MudV2Test } from "@latticexyz/std-contracts/src/test/MudV2Test.t.sol";
// new import
import { MudTest } from "@latticexyz/store/src/MudTest.sol";
Refactor StoreSwitch
to use a storage slot instead of function isStore()
to determine which contract is Store:
StoreSwitch
called isStore()
on msg.sender
to determine if msg.sender
is a Store
contract. If the call succeeded, the Store
methods were called on msg.sender
, otherwise the data was written to the own storage.StoreSwitch
instead checks for an address
in a known storage slot. If the address equals the own address, data is written to the own storage. If it is an external address, Store
methods are called on this address. If it is unset (address(0)
), store methods are called on msg.sender
.World
contracts sets its own address in StoreSwitch
, while System
contracts keep the Store address undefined, so Systems
write to their caller (World
) if they are executed via call
or directly to the World
storage if they are executed via delegatecall
.Systems
to explicitly set a Store
address to make them exclusive to that Store
andStore
argument, because the MudTest
base contract redirects reads and writes to the internal World
contract.93390d89: Added an abstract
StoreKernel
contract, which includes all Store interfaces except for registration, and implements write methods, protocolVersion
and initializes StoreCore
. Store
extends StoreKernel
with the IStoreRegistration
interface. StoreData
is removed as a separate interface/contract. World
now extends StoreKernel
(since the registration methods are added via the InitModule
).
144c0d8d: Replaced the static array length getters in table libraries with constants.
9b43029c3: Add protocol version with corresponding getter and event on deploy
world.worldVersion();
world.storeVersion(); // a World is also a Store
event HelloWorld(bytes32 indexed worldVersion);
event HelloStore(bytes32 indexed storeVersion);
55ab88a60: 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);
}
80dd6992e: Add an optional namePrefix
argument to renderRecordData
, to support inlined logic in codegenned set
method which uses a struct.
708b49c50: 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
}
3ac68ade6: Removed allowEmpty
option from FieldLayout.validate()
as field layouts should never be empty.
3042f86e: Moved key schema and value schema methods to constants in code-generated table libraries for less bytecode and less gas in register/install methods.
-console.log(SomeTable.getKeySchema());
+console.log(SomeTable._keySchema);
-console.log(SomeTable.getValueSchema());
+console.log(SomeTable._valueSchema);
5e71e1cb5: Moved KeySchema
, ValueSchema
, SchemaToPrimitives
and TableRecord
types into @latticexyz/protocol-parser
d7b1c588a: Upgraded all packages and templates to viem v2.7.12 and abitype v1.0.0.
Some viem APIs have changed and we've updated getContract
to reflect those changes and keep it aligned with viem. It's one small code change:
const worldContract = getContract({
address: worldAddress,
abi: IWorldAbi,
- publicClient,
- walletClient,
+ client: { public: publicClient, wallet: walletClient },
});
8025c3505: We now use @latticexyz/abi-ts
to generate TS type declaration files (.d.ts
) for each ABI JSON file. This replaces our usage TypeChain everywhere.
If you previously relied on TypeChain types from @latticexyz/store
or @latticexyz/world
, you will either need to migrate to viem or abitype using ABI JSON imports or generate TypeChain types from our exported ABI JSON files.
import { getContract } from "viem";
import IStoreAbi from "@latticexyz/store/abi/IStore.sol/IStore.abi.json";
const storeContract = getContract({
abi: IStoreAbi,
...
});
await storeContract.write.setRecord(...);
103f635eb: Improved error messages for invalid FieldLayout
s
-error FieldLayoutLib_InvalidLength(uint256 length);
+error FieldLayoutLib_TooManyFields(uint256 numFields, uint256 maxFields);
+error FieldLayoutLib_TooManyDynamicFields(uint256 numFields, uint256 maxFields);
+error FieldLayoutLib_Empty();
d8c8f66bf: Exclude ERC165 interface ID from custom interface ID's.
c6c13f2ea: Storage events are now emitted after "before" hooks, so that the resulting logs are now correctly ordered and reflect onchain logic. This resolves issues with store writes and event emissions happening in "before" hooks.
1b86eac05: Changed the type of the output variable in the slice4
function to bytes4
.
a35c05ea9: Table libraries now hardcode the bytes32
table ID value rather than computing it in Solidity. This saves a bit of gas across all storage operations.
c963b46c7: Optimize storage library
05b3e8882: Fixed a race condition when registering core tables, where we would set a record in the ResourceIds
table before the table was registered.
aea67c580: Include bytecode for World
and Store
in npm packages.
90e4161bb: Moved the test tables out of the main config in world
and store
and into their own separate config.
904fd7d4e: Add store sync package
e6c03a87a: Renamed the requireNoCallback
modifier to prohibitDirectCallback
.
1077c7f53: Fixed an issue where mud.config.ts
source file was not included in the package, causing TS errors downstream.
2c920de7: Refactored StoreCore
to import IStoreEvents
instead of defining the events twice.
44236041f: Moved table ID and field layout constants in code-generated table libraries from the file level into the library, for clearer access and cleaner imports.
-import { SomeTable, SomeTableTableId } from "./codegen/tables/SomeTable.sol";
+import { SomeTable } from "./codegen/tables/SomeTable.sol";
-console.log(SomeTableTableId);
+console.log(SomeTable._tableId);
-console.log(SomeTable.getFieldLayout());
+console.log(SomeTable._fieldLayout);
f62c767e7: Parallelized table codegen. Also put logs behind debug flag, which can be enabled using the DEBUG=mud:*
environment variable.
37c228c63: Refactored various files to specify integers in a hex base instead of decimals.
c991c71a: Added interfaces for all errors that are used by StoreCore
, which includes FieldLayout
, PackedCounter
, Schema
, and Slice
. This interfaces are inherited by IStore
, ensuring that all possible errors are included in the IStore
ABI for proper decoding in the frontend.
1bf2e9087: Updated codegen to not render push
and pop
methods for static arrays. The length
method now returns the hardcoded known length instead of calculating it like with a dynamic array.
211be2a1e: 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.
0f3e2e02b: Added Storage.loadField
to optimize loading 32 bytes or less from storage (which is always the case when loading data for static fields).
d08789282: Prefixed all errors with their respective library/contract for improved debugging.
5c965a919: Align Store events parameter naming between IStoreWrite and StoreCore
f99e88987: Bump viem to 1.14.0 and abitype to 0.9.8
d5b73b126: Optimize autogenerated table libraries
190fdd11: Restored Bytes.sliceN
helpers that were previously (mistakenly) removed and renamed them to Bytes.getBytesN
.
If you're upgrading an existing MUD project, you can rerun codegen with mud build
to update your table libraries to the new function names.
b2d2aa715: Added an explicit package export for mud.config
5e723b90e: The ResourceType
table is removed.
It was previously used to store the resource type for each resource ID in a World
. This is no longer necessary as the resource type is now encoded in the resource ID.
To still be able to determine whether a given resource ID exists, a ResourceIds
table has been added.
The previous ResourceType
table was part of World
and missed tables that were registered directly via StoreCore.registerTable
instead of via World.registerTable
(e.g. when a table was registered as part of a root module).
This problem is solved by the new table ResourceIds
being part of Store
.
StoreCore
's hasTable
function was removed in favor of using ResourceIds.getExists(tableId)
directly.
- import { ResourceType } from "@latticexyz/world/src/tables/ResourceType.sol";
- import { StoreCore } from "@latticexyz/store/src/StoreCore.sol";
+ import { ResourceIds } from "@latticexyz/store/src/codegen/tables/ResourceIds.sol";
- bool tableExists = StoreCore.hasTable(tableId);
+ bool tableExists = ResourceIds.getExists(tableId);
- bool systemExists = ResourceType.get(systemId) != Resource.NONE;
+ bool systemExists = ResourceIds.getExists(systemId);
6573e38e9: 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
);
37c228c63: Refactored ResourceId
to use a global Solidity using
statement.
37c228c63: Refactored EIP165 usages to use the built-in interfaceId property instead of pre-defined constants.
6e66c5b74: 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
);
8d51a0348: Clean up Memory.sol, make mcopy pure
48909d151: bump forge-std and ds-test dependencies
7b28d32e5: Added a custom error Store_InvalidBounds
for when the start:end
slice in getDynamicFieldSlice
is invalid (it used to revert with the default overflow error)
590542030: TS packages now generate their respective .d.ts
type definition files for better compatibility when using MUD with moduleResolution
set to bundler
or node16
and fixes issues around missing type declarations for dependent packages.
48c51b52a: RECS components are now dynamically created and inferred from your MUD config when using syncToRecs
.
To migrate existing projects after upgrading to this MUD version:
Remove contractComponents.ts
from client/src/mud
Remove components
argument from syncToRecs
Update build:mud
and dev
scripts in contracts/package.json
to remove tsgen
- "build:mud": "mud tablegen && mud worldgen && mud tsgen --configPath mud.config.ts --out ../client/src/mud",
+ "build:mud": "mud tablegen && mud worldgen",
- "dev": "pnpm mud dev-contracts --tsgenOutput ../client/src/mud",
+ "dev": "pnpm mud dev-contracts",
9f8b84e73: Aligned the order of function arguments in the Storage
library.
store(uint256 storagePointer, uint256 offset, bytes memory data)
store(uint256 storagePointer, uint256 offset, uint256 length, uint256 memoryPointer)
load(uint256 storagePointer, uint256 offset, uint256 length)
load(uint256 storagePointer, uint256 offset, uint256 length, uint256 memoryPointer)
55a05fd7a: Refactored StoreCore.registerStoreHook
to use StoreHooks._push
for gas efficiency.
63831a264: Minor Store
cleanups: renamed Utils.sol
to leftMask.sol
since it only contains a single free function, and removed a leftover sanity check.
6db95ce15: Fixed StoreCore
to pass previousEncodedLengths
into onBeforeSpliceDynamicData
.
5d737cf2e: Updated the debug
util to pipe to stdout
and added an additional util to explicitly pipe to stderr
when needed.
22ee44700: All Store
and World
tables now use the appropriate user-types for ResourceId
, FieldLayout
and Schema
to avoid manual wrap
/unwrap
.
ad4ac4459: Added more validation checks for FieldLayout
and Schema
.
be313068b: Optimized the StoreCore
hash function determining the data location to use less gas.
bb91edaa0: Fixed resolveUserTypes
for static arrays.
resolveUserTypes
is used by deploy
, which prevented deploying tables with static arrays.
5ac4c97f4: Fixed M-04 Memory Corruption on Load From Storage
It only affected external use of Storage.load
with a memoryPointer
argument
e48171741: Removed unused imports from various files in the store
and world
packages.
37c228c63: Refactored various Solidity files to not explicitly initialise variables to zero.
c58da9ad: Moved the HelloStore
to IStoreEvents
so all Store events are defined in the same interface.
37c228c63: Refactored some Store functions to use a right bit mask instead of left.
535229984: - bump to viem 1.3.0 and abitype 0.9.3
@wagmi/chains
imports to viem/chains
0d12db8c2: Optimize Schema methods.
Return uint256
instead of uint8
in SchemaInstance numFields methods
37c228c63: Simplified a check in Slice.getSubslice
.
22ba7b675: Simplified a couple internal constants used for bitshifting.
745485cda: Updated StoreCore
to check that tables exist before registering store hooks.
37c228c63: Optimised the Schema.validate
function to decrease gas use.
cc2c8da00: - Refactor tightcoder to use typescript functions instead of ejs
TightCoder
libraryisLeftAligned
and getLeftPaddingBits
common codegen helpersUpdated dependencies [a35c05ea9]
Updated dependencies [16b13ea8f]
Updated dependencies [82693072]
Updated dependencies [07dd6f32c]
Updated dependencies [aabd30767]
Updated dependencies [65c9546c4]
Updated dependencies [d5c0682fb]
Updated dependencies [01e46d99]
Updated dependencies [904fd7d4e]
Updated dependencies [b98e51808]
Updated dependencies [331dbfdcb]
Updated dependencies [44236041f]
Updated dependencies [066056154]
Updated dependencies [3fb9ce283]
Updated dependencies [bb6ada740]
Updated dependencies [35c9f33df]
Updated dependencies [0b8ce3f2c]
Updated dependencies [933b54b5f]
Updated dependencies [307abab3]
Updated dependencies [de151fec0]
Updated dependencies [aacffcb59]
Updated dependencies [b38c096d]
Updated dependencies [4bb7e8cbf]
Updated dependencies [f99e88987]
Updated dependencies [939916bcd]
Updated dependencies [e34d1170]
Updated dependencies [b8a6158d6]
Updated dependencies [433078c54]
Updated dependencies [db314a74]
Updated dependencies [ca50fef81]
Updated dependencies [59267655]
Updated dependencies [8d51a0348]
Updated dependencies [c162ad5a5]
Updated dependencies [48909d151]
Updated dependencies [f8a01a047]
Updated dependencies [b02f9d0e4]
Updated dependencies [f62c767e7]
Updated dependencies [bb91edaa0]
Updated dependencies [590542030]
Updated dependencies [1b5eb0d07]
Updated dependencies [44a5432ac]
Updated dependencies [f03531d97]
Updated dependencies [b8a6158d6]
Updated dependencies [5d737cf2e]
Updated dependencies [d075f82f3]
Updated dependencies [331dbfdcb]
Updated dependencies [92de59982]
Updated dependencies [9ff4dd955]
Updated dependencies [bfcb293d1]
Updated dependencies [3e057061d]
Updated dependencies [535229984]
Updated dependencies [5e723b90e]
Updated dependencies [0c4f9fea9]
Updated dependencies [60cfd089f]
Updated dependencies [24a6cd536]
Updated dependencies [708b49c50]
Updated dependencies [d2f8e9400]
Updated dependencies [25086be5f]
Updated dependencies [b1d41727d]
Updated dependencies [4c1dcd81e]
Updated dependencies [6071163f7]
Updated dependencies [6c6733256]
Updated dependencies [cd5abcc3b]
Updated dependencies [d7b1c588a]
Updated dependencies [c4f49240d]
Updated dependencies [3e7d83d0]
Updated dependencies [5df1f31bc]
Updated dependencies [a2f41ade9]
Updated dependencies [cea754dde]
Updated dependencies [5e71e1cb5]
Updated dependencies [331f0d636]
Updated dependencies [cc2c8da00]
Published by github-actions[bot] 7 months ago
Published by github-actions[bot] 7 months ago
Published by github-actions[bot] 7 months ago
433078c54: Reverse PackedCounter encoding, to optimize gas for bitshifts.
Ints are right-aligned, shifting using an index is straightforward if they are indexed right-to-left.
331f0d636: Move createFaucetService
from @latticexyz/network
to @latticexyz/services/faucet
.
- import { createFaucetService } from "@latticexyz/network";
+ import { createFaucetService } from "@latticexyz/services/faucet";
.d.ts
type definition files for better compatibility when using MUD with moduleResolution
set to bundler
or node16
and fixes issues around missing type declarations for dependent packages.Published by github-actions[bot] 7 months ago
/internal
import path to indicate that these are now internal-only and deprecated. We'll be replacing these types and functions with new ones that are compatible with our new, strongly-typed config.b02f9d0e4: add type narrowing isStaticAbiType
bb91edaa0: Added isSchemaAbiType
helper function to check and narrow an unknown string to the SchemaAbiType
type
d7b1c588a: Upgraded all packages and templates to viem v2.7.12 and abitype v1.0.0.
Some viem APIs have changed and we've updated getContract
to reflect those changes and keep it aligned with viem. It's one small code change:
const worldContract = getContract({
address: worldAddress,
abi: IWorldAbi,
- publicClient,
- walletClient,
+ client: { public: publicClient, wallet: walletClient },
});
.d.ts
type definition files for better compatibility when using MUD with moduleResolution
set to bundler
or node16
and fixes issues around missing type declarations for dependent packages.uint64
and int64
.@wagmi/chains
imports to viem/chains
Published by github-actions[bot] 7 months ago
useRow
and useRows
hooks, previously powered by store-cache
, which is now deprecated. Please use recs
and the corresponding useEntityQuery
and useComponentValue
hooks. We'll have more hooks soon for SQL.js sync backends.939916bcd: Adds a usePromise
hook that returns a native PromiseSettledResult
object.
const promise = fetch(url);
const result = usePromise(promise);
if (result.status === "idle" || result.status === "pending") {
return <>fetching</>;
}
if (result.status === "rejected") {
return <>error fetching: {String(result.reason)}</>;
}
if (result.status === "fulfilled") {
return <>fetch status: {result.value.status}</>;
}
curry
in @latticexyz/common
and useDeprecatedComputedValue
from @latticexyz/react
..d.ts
type definition files for better compatibility when using MUD with moduleResolution
set to bundler
or node16
and fixes issues around missing type declarations for dependent packages.useComponentValue
would not detect a change and re-render if the component value was immediately removed.Published by github-actions[bot] 7 months ago
c14f8bf1e: - Moved createActionSystem
from std-client
to recs
package and updated it to better support v2 sync stack.
If you want to use createActionSystem
alongside syncToRecs
, you'll need to pass in arguments like so:
import { syncToRecs } from "@latticexyz/store-sync/recs";
import { createActionSystem } from "@latticexyz/recs/deprecated";
import { from, mergeMap } from "rxjs";
const { blockLogsStorage$, waitForTransaction } = syncToRecs({
world,
...
});
const txReduced$ = blockLogsStorage$.pipe(
mergeMap(({ operations }) => from(operations.map((op) => op.log?.transactionHash).filter(isDefined)))
);
const actionSystem = createActionSystem(world, txReduced$, waitForTransaction);
Fixed a bug in waitForComponentValueIn
that caused the promise to not resolve if the component value was already set when the function was called.
Fixed a bug in createActionSystem
that caused optimistic updates to be incorrectly propagated to requirement checks. To fix the bug, you must now pass in the full component object to the action's updates
instead of just the component name.
actions.add({
updates: () => [
{
- component: "Resource",
+ component: Resource,
...
}
],
...
});
ce7125a1b: Removes solecs
package. These were v1 contracts, now entirely replaced by our v2 tooling. See the MUD docs for building with v2 or create a new project from our v2 templates with pnpm create mud@next your-app-name
.
1e2ad78e2: improve RECS error messages for v2 components
590542030: TS packages now generate their respective .d.ts
type definition files for better compatibility when using MUD with moduleResolution
set to bundler
or node16
and fixes issues around missing type declarations for dependent packages.
60cfd089f: Templates and examples now use MUD's new sync packages, all built on top of viem. This greatly speeds up and stabilizes our networking code and improves types throughout.
These new sync packages come with support for our recs
package, including encodeEntity
and decodeEntity
utilities for composite keys.
If you're using store-cache
and useRow
/useRows
, you should wait to upgrade until we have a suitable replacement for those libraries. We're working on a sql.js-powered sync module that will replace store-cache
.
Migrate existing RECS apps to new sync packages
As you migrate, you may find some features replaced, removed, or not included by default. Please open an issue and let us know if we missed anything.
Add @latticexyz/store-sync
package to your app's client
package and make sure viem
is pinned to version 1.3.1
(otherwise you may get type errors)
In your supportedChains.ts
, replace foundry
chain with our new mudFoundry
chain.
- import { foundry } from "viem/chains";
- import { MUDChain, latticeTestnet } from "@latticexyz/common/chains";
+ import { MUDChain, latticeTestnet, mudFoundry } from "@latticexyz/common/chains";
- export const supportedChains: MUDChain[] = [foundry, latticeTestnet];
+ export const supportedChains: MUDChain[] = [mudFoundry, latticeTestnet];
In getNetworkConfig.ts
, remove the return type (to let TS infer it for now), remove now-unused config values, and add the viem chain
object.
- export async function getNetworkConfig(): Promise<NetworkConfig> {
+ export async function getNetworkConfig() {
const initialBlockNumber = params.has("initialBlockNumber")
? Number(params.get("initialBlockNumber"))
- : world?.blockNumber ?? -1; // -1 will attempt to find the block number from RPC
+ : world?.blockNumber ?? 0n;
+ return {
+ privateKey: getBurnerWallet().value,
+ chain,
+ worldAddress,
+ initialBlockNumber,
+ faucetServiceUrl: params.get("faucet") ?? chain.faucetUrl,
+ };
In setupNetwork.ts
, replace setupMUDV2Network
with syncToRecs
.
- import { setupMUDV2Network } from "@latticexyz/std-client";
- import { createFastTxExecutor, createFaucetService, getSnapSyncRecords } from "@latticexyz/network";
+ import { createFaucetService } from "@latticexyz/network";
+ import { createPublicClient, fallback, webSocket, http, createWalletClient, getContract, Hex, parseEther, ClientConfig } from "viem";
+ import { encodeEntity, syncToRecs } from "@latticexyz/store-sync/recs";
+ import { createBurnerAccount, createContract, transportObserver } from "@latticexyz/common";
- const result = await setupMUDV2Network({
- ...
- });
+ const clientOptions = {
+ chain: networkConfig.chain,
+ transport: transportObserver(fallback([webSocket(), http()])),
+ pollingInterval: 1000,
+ } as const satisfies ClientConfig;
+ const publicClient = createPublicClient(clientOptions);
+ const burnerAccount = createBurnerAccount(networkConfig.privateKey as Hex);
+ const burnerWalletClient = createWalletClient({
+ ...clientOptions,
+ account: burnerAccount,
+ });
+ const { components, latestBlock$, blockStorageOperations$, waitForTransaction } = await syncToRecs({
+ world,
+ config: storeConfig,
+ address: networkConfig.worldAddress as Hex,
+ publicClient,
+ components: contractComponents,
+ startBlock: BigInt(networkConfig.initialBlockNumber),
+ indexerUrl: networkConfig.indexerUrl ?? undefined,
+ });
+ const worldContract = createContract({
+ address: networkConfig.worldAddress as Hex,
+ abi: IWorld__factory.abi,
+ publicClient,
+ walletClient: burnerWalletClient,
+ });
// Request drip from faucet
- const signer = result.network.signer.get();
- if (networkConfig.faucetServiceUrl && signer) {
- const address = await signer.getAddress();
+ if (networkConfig.faucetServiceUrl) {
+ const address = burnerAccount.address;
const requestDrip = async () => {
- const balance = await signer.getBalance();
+ const balance = await publicClient.getBalance({ address });
console.info(`[Dev Faucet]: Player balance -> ${balance}`);
- const lowBalance = balance?.lte(utils.parseEther("1"));
+ const lowBalance = balance < parseEther("1");
You can remove the previous ethers worldContract
, snap sync code, and fast transaction executor.
The return of setupNetwork
is a bit different than before, so you may have to do corresponding app changes.
+ return {
+ world,
+ components,
+ playerEntity: encodeEntity({ address: "address" }, { address: burnerWalletClient.account.address }),
+ publicClient,
+ walletClient: burnerWalletClient,
+ latestBlock$,
+ blockStorageOperations$,
+ waitForTransaction,
+ worldContract,
+ };
Update createSystemCalls
with the new return type of setupNetwork
.
export function createSystemCalls(
- { worldSend, txReduced$, singletonEntity }: SetupNetworkResult,
+ { worldContract, waitForTransaction }: SetupNetworkResult,
{ Counter }: ClientComponents
) {
const increment = async () => {
- const tx = await worldSend("increment", []);
- await awaitStreamValue(txReduced$, (txHash) => txHash === tx.hash);
+ const tx = await worldContract.write.increment();
+ await waitForTransaction(tx);
return getComponentValue(Counter, singletonEntity);
};
(optional) If you still need a clock, you can create it with:
import { map, filter } from "rxjs";
import { createClock } from "@latticexyz/network";
const clock = createClock({
period: 1000,
initialTime: 0,
syncInterval: 5000,
});
world.registerDisposer(() => clock.dispose());
latestBlock$
.pipe(
map((block) => Number(block.timestamp) * 1000), // Map to timestamp in ms
filter((blockTimestamp) => blockTimestamp !== clock.lastUpdateTime), // Ignore if the clock was already refreshed with this block
filter((blockTimestamp) => blockTimestamp !== clock.currentTime), // Ignore if the current local timestamp is correct
)
.subscribe(clock.update); // Update the local clock
If you're using the previous LoadingState
component, you'll want to migrate to the new SyncProgress
:
import { SyncStep, singletonEntity } from "@latticexyz/store-sync/recs";
const syncProgress = useComponentValue(SyncProgress, singletonEntity, {
message: "Connecting",
percentage: 0,
step: SyncStep.INITIALIZE,
});
if (syncProgress.step === SyncStep.LIVE) {
// we're live!
}
afdba793f: Update RECS components with v2 key/value schemas. This helps with encoding/decoding composite keys and strong types for keys/values.
This may break if you were previously dependent on component.id
, component.metadata.componentId
, or component.metadata.tableId
:
component.id
is now the on-chain bytes32
hex representation of the table IDcomponent.metadata.componentName
is the table name (e.g. Position
)component.metadata.tableName
is the namespaced table name (e.g. myworld:Position
)component.metadata.keySchema
is an object with key names and their corresponding ABI typescomponent.metadata.valueSchema
is an object with field names and their corresponding ABI typesUpdated dependencies [52182f70d]
Updated dependencies [aabd30767]
Updated dependencies [b38c096d]
Updated dependencies [f99e88987]
Updated dependencies [48909d151]
Updated dependencies [b02f9d0e4]
Updated dependencies [bb91edaa0]
Updated dependencies [590542030]
Updated dependencies [f03531d97]
Updated dependencies [b8a6158d6]
Updated dependencies [92de59982]
Updated dependencies [4e4a34150]
Updated dependencies [535229984]
Updated dependencies [d7b1c588a]
Published by github-actions[bot] 7 months ago
Published by github-actions[bot] 7 months ago
07dd6f32c: 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",
},
},
}
}
331dbfdcb: readHex
was moved from @latticexyz/protocol-parser
to @latticexyz/common
b38c096d: Moved all existing exports to a /internal
import path to indicate that these are now internal-only and deprecated. We'll be replacing these types and functions with new ones that are compatible with our new, strongly-typed config.
433078c54: Reverse PackedCounter encoding, to optimize gas for bitshifts.
Ints are right-aligned, shifting using an index is straightforward if they are indexed right-to-left.
b98e51808: feat: add abiTypesToSchema, a util to turn a list of abi types into a Schema by separating static and dynamic types
de151fec0: - 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.
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
.
ca50fef81: feat: add encodeKeyTuple
, a util to encode key tuples in Typescript (equivalent to key tuple encoding in Solidity and inverse of decodeKeyTuple
).
Example:
encodeKeyTuple(
{
staticFields: ["uint256", "int32", "bytes16", "address", "bool", "int8"],
dynamicFields: [],
},
[42n, -42, "0x12340000000000000000000000000000", "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF", true, 3],
);
// [
// "0x000000000000000000000000000000000000000000000000000000000000002a",
// "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd6",
// "0x1234000000000000000000000000000000000000000000000000000000000000",
// "0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff",
// "0x0000000000000000000000000000000000000000000000000000000000000001",
// "0x0000000000000000000000000000000000000000000000000000000000000003",
// ]
9ff4dd955: Adds valueSchemaToFieldLayoutHex
helper
d7b1c588a: Upgraded all packages and templates to viem v2.7.12 and abitype v1.0.0.
Some viem APIs have changed and we've updated getContract
to reflect those changes and keep it aligned with viem. It's one small code change:
const worldContract = getContract({
address: worldAddress,
abi: IWorldAbi,
- publicClient,
- walletClient,
+ client: { public: publicClient, wallet: walletClient },
});
5e71e1cb5: 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.
decodeRecord
now properly decodes empty recordsvalueSchemaToFieldLayoutHex
helper.d.ts
type definition files for better compatibility when using MUD with moduleResolution
set to bundler
or node16
and fixes issues around missing type declarations for dependent packages.@wagmi/chains
imports to viem/chains
PackedCounter
to EncodedLengths
for consistency.