MUD is a framework for building ambitious onchain applications
MIT License
Bot releases are visible (Hide)
Published by github-actions[bot] about 1 year ago
Published by github-actions[bot] about 1 year ago
251170e1
Thanks @alvrs! - All optional modules have been moved from @latticexyz/world
to @latticexyz/world-modules
.#1592 c07fa021
Thanks @alvrs! - Tables and interfaces in the world
package are now generated to the codegen
folder.
This is only a breaking change if you imported tables or codegenerated interfaces from @latticexyz/world
directly.
If you're using the MUD CLI, the changed import paths are already integrated and no further changes are necessary.
- import { IBaseWorld } from "@latticexyz/world/src/interfaces/IBaseWorld.sol";
+ import { IBaseWorld } from "@latticexyz/world/src/codegen/interfaces/IBaseWorld.sol";
#1601 1890f1a0
Thanks @alvrs! - Moved store
tables to the "store"
namespace (previously "mudstore") and world
tables to the "world"
namespace (previously root namespace).
Updated dependencies [77dce993
, 748f4588
, aea67c58
, 07dd6f32
, c07fa021
, 90e4161b
, 65c9546c
, 331dbfdc
, f9f9609e
, 331dbfdc
, 759514d8
, d5094a24
, 0b8ce3f2
, de151fec
, ae340b2b
, e5d208e4
, 211be2a1
, 0f3e2e02
, 1f80a0b5
, d0878928
, 4c7fd3eb
, a0341daf
, 83583a50
, 5e723b90
, 6573e38e
, 44a5432a
, 6e66c5b7
, 65c9546c
, 44a5432a
, 672d05ca
, f1cd43bf
, 31ffc9d5
, 5e723b90
, 63831a26
, 331dbfdc
, 92de5998
, 5741d53d
, 22ee4470
, be313068
, ac508bf1
, bfcb293d
, 1890f1a0
, 9b43029c
, 55ab88a6
, af639a26
, 5e723b90
, 99ab9cd6
, c049c23f
, 80dd6992
, 24a6cd53
, 708b49c5
, 22ba7b67
, c049c23f
, 251170e1
, c4f49240
, cea754dd
, 95c59b20
]:
Published by github-actions[bot] about 1 year ago
#1606 77dce993
Thanks @holic! - Moves World interfaces and factories files for consistency with our other packages.
If you import any World interfaces or factories directly, you'll need to update the import path:
- import { IBaseWorld } from "@latticexyz/world/src/interfaces/IBaseWorld.sol";
+ import { IBaseWorld } from "@latticexyz/world/src/IBaseWorld.sol";
- import { IBaseWorld } from "@latticexyz/world/src/factories/WorldFactory.sol";
+ import { IBaseWorld } from "@latticexyz/world/src/WorldFactory.sol";
#1563 748f4588
Thanks @alvrs! - All World
methods now revert if the World
calls itself.
The World
should never need to externally call itself, since all internal table operations happen via library calls, and all root system operations happen via delegate call.
It should not be possible to make the World
call itself as an external actor.
If it were possible to make the World
call itself, it would be possible to write to internal tables that only the World
should have access to.
As this is a very important invariance, we made it explicit in a requirement check in every World
method, rather than just relying on making it impossible to trigger the World
to call itself.
This is a breaking change for modules that previously used external calls to the World
in the installRoot
method.
In the installRoot
method, the World
can only be called via delegatecall
, and table operations should be performed via the internal table methods (e.g. _set
instead of set
).
Example for how to replace external calls to world
in root systems / root modules (installRoot
) with delegatecall
:
+ import { revertWithBytes } from "@latticexyz/world/src/revertWithBytes.sol";
- world.grantAccess(tableId, address(hook));
+ (bool success, bytes memory returnData) = address(world).delegatecall(
+ abi.encodeCall(world.grantAccess, (tableId, address(hook)))
+ );
+ if (!success) revertWithBytes(returnData);
#1592 c07fa021
Thanks @alvrs! - Tables and interfaces in the world
package are now generated to the codegen
folder.
This is only a breaking change if you imported tables or codegenerated interfaces from @latticexyz/world
directly.
If you're using the MUD CLI, the changed import paths are already integrated and no further changes are necessary.
- import { IBaseWorld } from "@latticexyz/world/src/interfaces/IBaseWorld.sol";
+ import { IBaseWorld } from "@latticexyz/world/src/codegen/interfaces/IBaseWorld.sol";
#1354 331dbfdc
Thanks @dk1a! - We've updated Store events to be "schemaless", meaning there is enough information in each event to only need to operate on the bytes of each record to make an update to that record without having to first decode the record by its schema. This enables new kinds of indexers and sync strategies.
If you've written your own sync logic or are interacting with Store calls directly, this is a breaking change. We have a few more breaking protocol changes upcoming, so you may hold off on upgrading until those land.
If you are using MUD's built-in tooling (table codegen, indexer, store sync, etc.), you don't have to make any changes except upgrading to the latest versions and deploying a fresh World.
The data
field in each StoreSetRecord
and StoreEphemeralRecord
has been replaced with three new fields: staticData
, encodedLengths
, and dynamicData
. This better reflects the on-chain state and makes it easier to perform modifications to the raw bytes. We recommend storing each of these fields individually in your off-chain storage of choice (indexer, client, etc.).
- event StoreSetRecord(bytes32 tableId, bytes32[] keyTuple, bytes data);
+ event StoreSetRecord(bytes32 tableId, bytes32[] keyTuple, bytes staticData, bytes32 encodedLengths, bytes dynamicData);
- event StoreEphemeralRecord(bytes32 tableId, bytes32[] keyTuple, bytes data);
+ event StoreEphemeralRecord(bytes32 tableId, bytes32[] keyTuple, bytes staticData, bytes32 encodedLengths, bytes dynamicData);
The StoreSetField
event is now replaced by two new events: StoreSpliceStaticData
and StoreSpliceDynamicData
. Splicing allows us to perform efficient operations like push and pop, in addition to replacing a field value. We use two events because updating a dynamic-length field also requires updating the record's encodedLengths
(aka PackedCounter).
- event StoreSetField(bytes32 tableId, bytes32[] keyTuple, uint8 fieldIndex, bytes data);
+ event StoreSpliceStaticData(bytes32 tableId, bytes32[] keyTuple, uint48 start, uint40 deleteCount, bytes data);
+ event StoreSpliceDynamicData(bytes32 tableId, bytes32[] keyTuple, uint48 start, uint40 deleteCount, bytes data, bytes32 encodedLengths);
Similarly, Store setter methods (e.g. setRecord
) have been updated to reflect the data
to staticData
, encodedLengths
, and dynamicData
changes. We'll be following up shortly with Store getter method changes for more gas efficient storage reads.
#1527 759514d8
Thanks @holic! - Moved the registration of store hooks and systems hooks to bitmaps with bitwise operator instead of a struct.
- import { StoreHookLib } from "@latticexyz/src/StoreHook.sol";
+ import {
+ BEFORE_SET_RECORD,
+ BEFORE_SET_FIELD,
+ BEFORE_DELETE_RECORD
+ } from "@latticexyz/store/storeHookTypes.sol";
StoreCore.registerStoreHook(
tableId,
subscriber,
- StoreHookLib.encodeBitmap({
- onBeforeSetRecord: true,
- onAfterSetRecord: false,
- onBeforeSetField: true,
- onAfterSetField: false,
- onBeforeDeleteRecord: true,
- onAfterDeleteRecord: false
- })
+ BEFORE_SET_RECORD | BEFORE_SET_FIELD | BEFORE_DELETE_RECORD
);
- import { SystemHookLib } from "../src/SystemHook.sol";
+ import { BEFORE_CALL_SYSTEM, AFTER_CALL_SYSTEM } from "../src/systemHookTypes.sol";
world.registerSystemHook(
systemId,
subscriber,
- SystemHookLib.encodeBitmap({ onBeforeCallSystem: true, onAfterCallSystem: true })
+ BEFORE_CALL_SYSTEM | AFTER_CALL_SYSTEM
);
#1531 d5094a24
Thanks @alvrs! - - The IStoreHook
interface was changed to replace onBeforeSetField
and onAfterSetField
with onBeforeSpliceStaticData
, onAfterSpliceStaticData
, onBeforeSpliceDynamicData
and onAfterSpliceDynamicData
.
This new interface matches the new StoreSpliceStaticData
and StoreSpliceDynamicData
events, and avoids having to read the entire field from storage when only a subset of the field was updated
(e.g. when pushing elements to a field).
interface IStoreHook {
- function onBeforeSetField(
- bytes32 tableId,
- bytes32[] memory keyTuple,
- uint8 fieldIndex,
- bytes memory data,
- FieldLayout fieldLayout
- ) external;
- function onAfterSetField(
- bytes32 tableId,
- bytes32[] memory keyTuple,
- uint8 fieldIndex,
- bytes memory data,
- FieldLayout fieldLayout
- ) external;
+ function onBeforeSpliceStaticData(
+ bytes32 tableId,
+ bytes32[] memory keyTuple,
+ uint48 start,
+ uint40 deleteCount,
+ bytes memory data
+ ) external;
+ function onAfterSpliceStaticData(
+ bytes32 tableId,
+ bytes32[] memory keyTuple,
+ uint48 start,
+ uint40 deleteCount,
+ bytes memory data
+ ) external;
+ function onBeforeSpliceDynamicData(
+ bytes32 tableId,
+ bytes32[] memory keyTuple,
+ uint8 dynamicFieldIndex,
+ uint40 startWithinField,
+ uint40 deleteCount,
+ bytes memory data,
+ PackedCounter encodedLengths
+ ) external;
+ function onAfterSpliceDynamicData(
+ bytes32 tableId,
+ bytes32[] memory keyTuple,
+ uint8 dynamicFieldIndex,
+ uint40 startWithinField,
+ uint40 deleteCount,
+ bytes memory data,
+ PackedCounter encodedLengths
+ ) external;
}
All calldata
parameters on the IStoreHook
interface were changed to memory
, since the functions are called with memory
from the World
.
IStore
exposes two new functions: spliceStaticData
and spliceDynamicData
.
These functions provide lower level access to the operations happening under the hood in setField
, pushToField
, popFromField
and updateInField
and simplify handling
the new splice hooks.
StoreCore
's internal logic was simplified to use the spliceStaticData
and spliceDynamicData
functions instead of duplicating similar logic in different functions.
interface IStore {
// Splice data in the static part of the record
function spliceStaticData(
bytes32 tableId,
bytes32[] calldata keyTuple,
uint48 start,
uint40 deleteCount,
bytes calldata data
) external;
// Splice data in the dynamic part of the record
function spliceDynamicData(
bytes32 tableId,
bytes32[] calldata keyTuple,
uint8 dynamicFieldIndex,
uint40 startWithinField,
uint40 deleteCount,
bytes calldata data
) external;
}
#1336 de151fec
Thanks @dk1a! - - Add FieldLayout
, which is a bytes32
user-type similar to Schema
.
Both FieldLayout
and Schema
have the same kind of data in the first 4 bytes.
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
.
#1532 ae340b2b
Thanks @dk1a! - 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);
#1575 e5d208e4
Thanks @alvrs! - The registerRootFunctionSelector
function's signature was changed to accept a string functionSignature
parameter instead of a bytes4 functionSelector
parameter.
This change enables the World
to store the function signatures of all registered functions in a FunctionSignatures
offchain table, which will allow for the automatic generation of interfaces for a given World
address in the future.
IBaseWorld {
function registerRootFunctionSelector(
ResourceId systemId,
- bytes4 worldFunctionSelector,
+ string memory worldFunctionSignature,
bytes4 systemFunctionSelector
) external returns (bytes4 worldFunctionSelector);
}
#1483 83583a50
Thanks @holic! - 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";
#1574 31ffc9d5
Thanks @alvrs! - The registerFunctionSelector
function now accepts a single functionSignature
string paramemer instead of separating function name and function arguments into separate parameters.
IBaseWorld {
function registerFunctionSelector(
ResourceId systemId,
- string memory systemFunctionName,
- string memory systemFunctionArguments
+ string memory systemFunctionSignature
) external returns (bytes4 worldFunctionSelector);
}
This is a breaking change if you were manually registering function selectors, e.g. in a PostDeploy.s.sol
script or a module.
To upgrade, simply replace the separate systemFunctionName
and systemFunctionArguments
parameters with a single systemFunctionSignature
parameter.
world.registerFunctionSelector(
systemId,
- systemFunctionName,
- systemFunctionArguments,
+ string(abi.encodePacked(systemFunctionName, systemFunctionArguments))
);
#1544 5e723b90
Thanks @alvrs! - 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;
}
#1473 92de5998
Thanks @holic! - Bump Solidity version to 0.8.21
#1594 5741d53d
Thanks @alvrs! - - 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);
}
#1601 1890f1a0
Thanks @alvrs! - Moved store
tables to the "store"
namespace (previously "mudstore") and world
tables to the "world"
namespace (previously root namespace).
#1591 251170e1
Thanks @alvrs! - All optional modules have been moved from @latticexyz/world
to @latticexyz/world-modules
.
If you're using the MUD CLI, the import is already updated and no changes are necessary.
#1581 cea754dd
Thanks @alvrs! - - The external setRecord
and deleteRecord
methods of IStore
no longer accept a FieldLayout
as input, but load it from storage instead.
This is to prevent invalid FieldLayout
values being passed, which could cause the onchain state to diverge from the indexer state.
However, the internal StoreCore
library still exposes a setRecord
and deleteRecord
method that allows a FieldLayout
to be passed.
This is because StoreCore
can only be used internally, so the FieldLayout
value can be trusted and we can save the gas for accessing storage.
interface IStore {
function setRecord(
ResourceId tableId,
bytes32[] calldata keyTuple,
bytes calldata staticData,
PackedCounter encodedLengths,
bytes calldata dynamicData,
- FieldLayout fieldLayout
) external;
function deleteRecord(
ResourceId tableId,
bytes32[] memory keyTuple,
- FieldLayout fieldLayout
) external;
}
The spliceStaticData
method and Store_SpliceStaticData
event of IStore
and StoreCore
no longer include deleteCount
in their signature.
This is because when splicing static data, the data after start
is always overwritten with data
instead of being shifted, so deleteCount
is always the length of the data to be written.
event Store_SpliceStaticData(
ResourceId indexed tableId,
bytes32[] keyTuple,
uint48 start,
- uint40 deleteCount,
bytes data
);
interface IStore {
function spliceStaticData(
ResourceId tableId,
bytes32[] calldata keyTuple,
uint48 start,
- uint40 deleteCount,
bytes calldata data
) external;
}
The updateInField
method has been removed from IStore
, as it's almost identical to the more general spliceDynamicData
.
If you're manually calling updateInField
, here is how to upgrade to spliceDynamicData
:
- store.updateInField(tableId, keyTuple, fieldIndex, startByteIndex, dataToSet, fieldLayout);
+ uint8 dynamicFieldIndex = fieldIndex - fieldLayout.numStaticFields();
+ store.spliceDynamicData(tableId, keyTuple, dynamicFieldIndex, uint40(startByteIndex), uint40(dataToSet.length), dataToSet);
All other methods that are only valid for dynamic fields (pushToField
, popFromField
, getFieldSlice
)
have been renamed to make this more explicit (pushToDynamicField
, popFromDynamicField
, getDynamicFieldSlice
).
Their fieldIndex
parameter has been replaced by a dynamicFieldIndex
parameter, which is the index relative to the first dynamic field (i.e. dynamicFieldIndex
= fieldIndex
- numStaticFields
).
The FieldLayout
parameter has been removed, as it was only used to calculate the dynamicFieldIndex
in the method.
interface IStore {
- function pushToField(
+ function pushToDynamicField(
ResourceId tableId,
bytes32[] calldata keyTuple,
- uint8 fieldIndex,
+ uint8 dynamicFieldIndex,
bytes calldata dataToPush,
- FieldLayout fieldLayout
) external;
- function popFromField(
+ function popFromDynamicField(
ResourceId tableId,
bytes32[] calldata keyTuple,
- uint8 fieldIndex,
+ uint8 dynamicFieldIndex,
uint256 byteLengthToPop,
- FieldLayout fieldLayout
) external;
- function getFieldSlice(
+ function getDynamicFieldSlice(
ResourceId tableId,
bytes32[] memory keyTuple,
- uint8 fieldIndex,
+ uint8 dynamicFieldIndex,
- FieldLayout fieldLayout,
uint256 start,
uint256 end
) external view returns (bytes memory data);
}
IStore
has a new getDynamicFieldLength
length method, which returns the byte length of the given dynamic field and doesn't require the FieldLayout
.
IStore {
+ function getDynamicFieldLength(
+ ResourceId tableId,
+ bytes32[] memory keyTuple,
+ uint8 dynamicFieldIndex
+ ) external view returns (uint256);
}
IStore
now has additional overloads for getRecord
, getField
, getFieldLength
and setField
that don't require a FieldLength
to be passed, but instead load it from storage.
IStore
now exposes setStaticField
and setDynamicField
to save gas by avoiding the dynamic inference of whether the field is static or dynamic.
The getDynamicFieldSlice
method no longer accepts reading outside the bounds of the dynamic field.
This is to avoid returning invalid data, as the data of a dynamic field is not deleted when the record is deleted, but only its length is set to zero.
#1590 1f80a0b5
Thanks @alvrs! - 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;
}
#1602 672d05ca
Thanks @holic! - - 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.
#1511 9b43029c
Thanks @holic! - 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);
#1472 c049c23f
Thanks @alvrs! - - The World
contract now has an initialize
function, which can be called once by the creator of the World to install the core module.
This change allows the registration of all core tables to happen in the CoreModule
, so no table metadata has to be included in the World
's bytecode.
interface IBaseWorld {
function initialize(IModule coreModule) public;
}
The World
contract now stores the original creator of the World
in an immutable state variable.
It is used internally to only allow the original creator to initialize the World
in a separate transaction.
interface IBaseWorld {
function creator() external view returns (address);
}
The deploy script is updated to use the World
's initialize
function to install the CoreModule
instead of registerRootModule
as before.
#1500 95c59b20
Thanks @yonadaaa! - 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);
}
#1490 aea67c58
Thanks @alvrs! - Include bytecode for World
and Store
in npm packages.
#1600 90e4161b
Thanks @alvrs! - Moved the test tables out of the main config in world
and store
and into their own separate config.
#1508 211be2a1
Thanks @Boffee! - The FieldLayout
in table libraries is now generated at compile time instead of dynamically in a table library function.
This significantly reduces gas cost in all table library functions.
#1568 d0878928
Thanks @alvrs! - Prefixed all errors with their respective library/contract for improved debugging.
#1501 4c7fd3eb
Thanks @alvrs! - Remove a workaround for the internal InstalledModules
table that is not needed anymore.
#1524 a0341daf
Thanks @holic! - Renamed all funcSelectorAndArgs
arguments to callData
for clarity.
#1544 5e723b90
Thanks @alvrs! - 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);
#1492 6e66c5b7
Thanks @alvrs! - Renamed all occurrences of key
where it is used as "key tuple" to keyTuple
.
This is only a breaking change for consumers who manually decode Store
events, but not for consumers who use the MUD libraries.
event StoreSetRecord(
bytes32 tableId,
- bytes32[] key,
+ bytes32[] keyTuple,
bytes data
);
event StoreSetField(
bytes32 tableId,
- bytes32[] key,
+ bytes32[] keyTuple,
uint8 fieldIndex,
bytes data
);
event StoreDeleteRecord(
bytes32 tableId,
- bytes32[] key,
+ bytes32[] keyTuple,
);
event StoreEphemeralRecord(
bytes32 tableId,
- bytes32[] key,
+ bytes32[] keyTuple,
bytes data
);
#1452 f1cd43bf
Thanks @alvrs! - Register Delegations
table in the CoreModule
#1586 22ee4470
Thanks @alvrs! - All Store
and World
tables now use the appropriate user-types for ResourceId
, FieldLayout
and Schema
to avoid manual wrap
/unwrap
.
#1509 be313068
Thanks @Boffee! - Optimized the StoreCore
hash function determining the data location to use less gas.
#1521 55ab88a6
Thanks @alvrs! - StoreCore
and IStore
now expose specific functions for getStaticField
and getDynamicField
in addition to the general getField
.
Using the specific functions reduces gas overhead because more optimized logic can be executed.
interface IStore {
/**
* Get a single static field from the given tableId and key tuple, with the given value field layout.
* Note: the field value is left-aligned in the returned bytes32, the rest of the word is not zeroed out.
* Consumers are expected to truncate the returned value as needed.
*/
function getStaticField(
bytes32 tableId,
bytes32[] calldata keyTuple,
uint8 fieldIndex,
FieldLayout fieldLayout
) external view returns (bytes32);
/**
* Get a single dynamic field from the given tableId and key tuple at the given dynamic field index.
* (Dynamic field index = field index - number of static fields)
*/
function getDynamicField(
bytes32 tableId,
bytes32[] memory keyTuple,
uint8 dynamicFieldIndex
) external view returns (bytes memory);
}
#1513 708b49c5
Thanks @Boffee! - Generated table libraries now have a set of functions prefixed with _
that always use their own storage for read/write.
This saves gas for use cases where the functionality to dynamically determine which Store
to use for read/write is not needed, e.g. root systems in a World
, or when using Store
without World
.
We decided to continue to always generate a set of functions that dynamically decide which Store
to use, so that the generated table libraries can still be imported by non-root systems.
library Counter {
// Dynamically determine which store to write to based on the context
function set(uint32 value) internal;
// Always write to own storage
function _set(uint32 value) internal;
// ... equivalent functions for all other Store methods
}
#1569 22ba7b67
Thanks @alvrs! - Simplified a couple internal constants used for bitshifting.
Updated dependencies [aea67c58
, 07dd6f32
, 90e4161b
, 65c9546c
, 331dbfdc
, f9f9609e
, 331dbfdc
, 759514d8
, d5094a24
, 0b8ce3f2
, de151fec
, ae340b2b
, 211be2a1
, 0f3e2e02
, d0878928
, 83583a50
, 5e723b90
, 6573e38e
, 44a5432a
, 6e66c5b7
, 65c9546c
, 44a5432a
, 672d05ca
, 63831a26
, 331dbfdc
, 92de5998
, 22ee4470
, be313068
, ac508bf1
, bfcb293d
, 1890f1a0
, 9b43029c
, 55ab88a6
, af639a26
, 5e723b90
, 99ab9cd6
, c049c23f
, 80dd6992
, 24a6cd53
, 708b49c5
, 22ba7b67
, c4f49240
, cea754dd
]:
Published by github-actions[bot] about 1 year ago
Published by github-actions[bot] about 1 year ago
#1482 07dd6f32
Thanks @alvrs! - Renamed all occurrences of schema
where it is used as "value schema" to valueSchema
to clearly distinguish it from "key schema".
The only breaking change for users is the change from schema
to valueSchema
in mud.config.ts
.
// mud.config.ts
export default mudConfig({
tables: {
CounterTable: {
keySchema: {},
- schema: {
+ valueSchema: {
value: "uint32",
},
},
}
}
#1354 331dbfdc
Thanks @dk1a! - We've updated Store events to be "schemaless", meaning there is enough information in each event to only need to operate on the bytes of each record to make an update to that record without having to first decode the record by its schema. This enables new kinds of indexers and sync strategies.
As such, we've replaced blockStorageOperations# @latticexyz/store-sync with
storedBlockLogs# @latticexyz/store-sync, a stream of simplified Store event logs after they've been synced to the configured storage adapter. These logs may not reflect exactly the events that are on chain when e.g. hydrating from an indexer, but they will still allow the client to "catch up" to the on-chain state of your tables.
#1484 6573e38e
Thanks @alvrs! - Renamed all occurrences of table
where it is used as "table ID" to tableId
.
This is only a breaking change for consumers who manually decode Store
events, but not for consumers who use the MUD libraries.
event StoreSetRecord(
- bytes32 table,
+ bytes32 tableId,
bytes32[] key,
bytes data
);
event StoreSetField(
- bytes32 table,
+ bytes32 tableId,
bytes32[] key,
uint8 fieldIndex,
bytes data
);
event StoreDeleteRecord(
- bytes32 table,
+ bytes32 tableId,
bytes32[] key
);
event StoreEphemeralRecord(
- bytes32 table,
+ bytes32 tableId,
bytes32[] key,
bytes data
);
#1492 6e66c5b7
Thanks @alvrs! - Renamed all occurrences of key
where it is used as "key tuple" to keyTuple
.
This is only a breaking change for consumers who manually decode Store
events, but not for consumers who use the MUD libraries.
event StoreSetRecord(
bytes32 tableId,
- bytes32[] key,
+ bytes32[] keyTuple,
bytes data
);
event StoreSetField(
bytes32 tableId,
- bytes32[] key,
+ bytes32[] keyTuple,
uint8 fieldIndex,
bytes data
);
event StoreDeleteRecord(
bytes32 tableId,
- bytes32[] key,
+ bytes32[] keyTuple,
);
event StoreEphemeralRecord(
bytes32 tableId,
- bytes32[] key,
+ bytes32[] keyTuple,
bytes data
);
#1488 7e6e5157
Thanks @holic! - Catch errors when parsing logs to tables and storage operations, log and skip
#1586 22ee4470
Thanks @alvrs! - All Store
and World
tables now use the appropriate user-types for ResourceId
, FieldLayout
and Schema
to avoid manual wrap
/unwrap
.
#1558 bfcb293d
Thanks @alvrs! - What used to be known as ephemeral
table is now called offchain
table.
The previous ephemeral
tables only supported an emitEphemeral
method, which emitted a StoreSetEphemeralRecord
event.
Now offchain
tables support all regular table methods, except partial operations on dynamic fields (push
, pop
, update
).
Unlike regular tables they don't store data on-chain but emit the same events as regular tables (StoreSetRecord
, StoreSpliceStaticData
, StoreDeleteRecord
), so their data can be indexed by offchain indexers/clients.
- EphemeralTable.emitEphemeral(value);
+ OffchainTable.set(value);
#1601 1890f1a0
Thanks @alvrs! - Moved store
tables to the "store"
namespace (previously "mudstore") and world
tables to the "world"
namespace (previously root namespace).
#1577 af639a26
Thanks @alvrs! - Store
events have been renamed for consistency and readability.
If you're parsing Store
events manually, you need to update your ABI.
If you're using the MUD sync stack, the new events are already integrated and no further changes are necessary.
- event StoreSetRecord(
+ event Store_SetRecord(
ResourceId indexed tableId,
bytes32[] keyTuple,
bytes staticData,
bytes32 encodedLengths,
bytes dynamicData
);
- event StoreSpliceStaticData(
+ event Store_SpliceStaticData(
ResourceId indexed tableId,
bytes32[] keyTuple,
uint48 start,
uint40 deleteCount,
bytes data
);
- event StoreSpliceDynamicData(
+ event Store_SpliceDynamicData(
ResourceId indexed tableId,
bytes32[] keyTuple,
uint48 start,
uint40 deleteCount,
bytes data,
bytes32 encodedLengths
);
- event StoreDeleteRecord(
+ event Store_DeleteRecord(
ResourceId indexed tableId,
bytes32[] keyTuple
);
#1581 cea754dd
Thanks @alvrs! - - The external setRecord
and deleteRecord
methods of IStore
no longer accept a FieldLayout
as input, but load it from storage instead.
This is to prevent invalid FieldLayout
values being passed, which could cause the onchain state to diverge from the indexer state.
However, the internal StoreCore
library still exposes a setRecord
and deleteRecord
method that allows a FieldLayout
to be passed.
This is because StoreCore
can only be used internally, so the FieldLayout
value can be trusted and we can save the gas for accessing storage.
interface IStore {
function setRecord(
ResourceId tableId,
bytes32[] calldata keyTuple,
bytes calldata staticData,
PackedCounter encodedLengths,
bytes calldata dynamicData,
- FieldLayout fieldLayout
) external;
function deleteRecord(
ResourceId tableId,
bytes32[] memory keyTuple,
- FieldLayout fieldLayout
) external;
}
The spliceStaticData
method and Store_SpliceStaticData
event of IStore
and StoreCore
no longer include deleteCount
in their signature.
This is because when splicing static data, the data after start
is always overwritten with data
instead of being shifted, so deleteCount
is always the length of the data to be written.
event Store_SpliceStaticData(
ResourceId indexed tableId,
bytes32[] keyTuple,
uint48 start,
- uint40 deleteCount,
bytes data
);
interface IStore {
function spliceStaticData(
ResourceId tableId,
bytes32[] calldata keyTuple,
uint48 start,
- uint40 deleteCount,
bytes calldata data
) external;
}
The updateInField
method has been removed from IStore
, as it's almost identical to the more general spliceDynamicData
.
If you're manually calling updateInField
, here is how to upgrade to spliceDynamicData
:
- store.updateInField(tableId, keyTuple, fieldIndex, startByteIndex, dataToSet, fieldLayout);
+ uint8 dynamicFieldIndex = fieldIndex - fieldLayout.numStaticFields();
+ store.spliceDynamicData(tableId, keyTuple, dynamicFieldIndex, uint40(startByteIndex), uint40(dataToSet.length), dataToSet);
All other methods that are only valid for dynamic fields (pushToField
, popFromField
, getFieldSlice
)
have been renamed to make this more explicit (pushToDynamicField
, popFromDynamicField
, getDynamicFieldSlice
).
Their fieldIndex
parameter has been replaced by a dynamicFieldIndex
parameter, which is the index relative to the first dynamic field (i.e. dynamicFieldIndex
= fieldIndex
- numStaticFields
).
The FieldLayout
parameter has been removed, as it was only used to calculate the dynamicFieldIndex
in the method.
interface IStore {
- function pushToField(
+ function pushToDynamicField(
ResourceId tableId,
bytes32[] calldata keyTuple,
- uint8 fieldIndex,
+ uint8 dynamicFieldIndex,
bytes calldata dataToPush,
- FieldLayout fieldLayout
) external;
- function popFromField(
+ function popFromDynamicField(
ResourceId tableId,
bytes32[] calldata keyTuple,
- uint8 fieldIndex,
+ uint8 dynamicFieldIndex,
uint256 byteLengthToPop,
- FieldLayout fieldLayout
) external;
- function getFieldSlice(
+ function getDynamicFieldSlice(
ResourceId tableId,
bytes32[] memory keyTuple,
- uint8 fieldIndex,
+ uint8 dynamicFieldIndex,
- FieldLayout fieldLayout,
uint256 start,
uint256 end
) external view returns (bytes memory data);
}
IStore
has a new getDynamicFieldLength
length method, which returns the byte length of the given dynamic field and doesn't require the FieldLayout
.
IStore {
+ function getDynamicFieldLength(
+ ResourceId tableId,
+ bytes32[] memory keyTuple,
+ uint8 dynamicFieldIndex
+ ) external view returns (uint256);
}
IStore
now has additional overloads for getRecord
, getField
, getFieldLength
and setField
that don't require a FieldLength
to be passed, but instead load it from storage.
IStore
now exposes setStaticField
and setDynamicField
to save gas by avoiding the dynamic inference of whether the field is static or dynamic.
The getDynamicFieldSlice
method no longer accepts reading outside the bounds of the dynamic field.
This is to avoid returning invalid data, as the data of a dynamic field is not deleted when the record is deleted, but only its length is set to zero.
Updated dependencies [77dce993
, 748f4588
, aea67c58
, 07dd6f32
, c07fa021
, 90e4161b
, 65c9546c
, 331dbfdc
, f9f9609e
, 331dbfdc
, 759514d8
, d5094a24
, 0b8ce3f2
, de151fec
, ae340b2b
, e5d208e4
, 211be2a1
, 0f3e2e02
, 1f80a0b5
, d0878928
, 4c7fd3eb
, a0341daf
, 83583a50
, 5e723b90
, 6573e38e
, 44a5432a
, 6e66c5b7
, 65c9546c
, f8a01a04
, 44a5432a
, 672d05ca
, f1cd43bf
, 31ffc9d5
, 5e723b90
, 63831a26
, 331dbfdc
, 92de5998
, 5741d53d
, 22ee4470
, be313068
, ac508bf1
, 9ff4dd95
, bfcb293d
, 1890f1a0
, 9b43029c
, 55ab88a6
, af639a26
, 5e723b90
, 99ab9cd6
, c049c23f
, 80dd6992
, 24a6cd53
, 708b49c5
, 22ba7b67
, c049c23f
, 251170e1
, c4f49240
, cea754dd
, 95c59b20
]:
Published by github-actions[bot] about 1 year ago
#1526 498d05e3
Thanks @holic! - You can now install and run @latticexyz/store-indexer
from the npm package itself, without having to clone/build the MUD repo:
npm install @latticexyz/store-indexer
npm sqlite-indexer
# or
npm postgres-indexer
or
npx -p @latticexyz/store-indexer sqlite-indexer
# or
npx -p @latticexyz/store-indexer postgres-indexer
The binary will also load the nearby .env
file for easier local configuration.
We've removed the CHAIN_ID
requirement and instead require just a RPC_HTTP_URL
or RPC_WS_URL
or both. You can now also adjust the polling interval with POLLING_INTERVAL
(defaults to 1000ms, which corresponds to MUD's default block time).
#1514 ed07018b
Thanks @holic! - Fixes postgres indexer stopping sync after it catches up to the latest block.
#1546 301bcb75
Thanks @holic! - Improves error message when parsing env variables
#1533 b3c22a18
Thanks @holic! - Added README and refactored handling of common environment variables
Updated dependencies [aea67c58
, 07dd6f32
, 90e4161b
, 65c9546c
, 331dbfdc
, f9f9609e
, 331dbfdc
, 759514d8
, d5094a24
, 0b8ce3f2
, de151fec
, ae340b2b
, 211be2a1
, 0f3e2e02
, d0878928
, 83583a50
, 5e723b90
, 6573e38e
, 44a5432a
, 6e66c5b7
, 65c9546c
, 44a5432a
, 672d05ca
, 7e6e5157
, 63831a26
, 331dbfdc
, 92de5998
, 22ee4470
, be313068
, ac508bf1
, 331dbfdc
, bfcb293d
, 1890f1a0
, 9b43029c
, 55ab88a6
, af639a26
, 5e723b90
, 99ab9cd6
, c049c23f
, 80dd6992
, 24a6cd53
, 708b49c5
, 22ba7b67
, c4f49240
, cea754dd
]:
Published by github-actions[bot] about 1 year ago
#1482 07dd6f32
Thanks @alvrs! - Renamed all occurrences of schema
where it is used as "value schema" to valueSchema
to clearly distinguish it from "key schema".
The only breaking change for users is the change from schema
to valueSchema
in mud.config.ts
.
// mud.config.ts
export default mudConfig({
tables: {
CounterTable: {
keySchema: {},
- schema: {
+ valueSchema: {
value: "uint32",
},
},
}
}
#1354 331dbfdc
Thanks @dk1a! - We've updated Store events to be "schemaless", meaning there is enough information in each event to only need to operate on the bytes of each record to make an update to that record without having to first decode the record by its schema. This enables new kinds of indexers and sync strategies.
If you've written your own sync logic or are interacting with Store calls directly, this is a breaking change. We have a few more breaking protocol changes upcoming, so you may hold off on upgrading until those land.
If you are using MUD's built-in tooling (table codegen, indexer, store sync, etc.), you don't have to make any changes except upgrading to the latest versions and deploying a fresh World.
The data
field in each StoreSetRecord
and StoreEphemeralRecord
has been replaced with three new fields: staticData
, encodedLengths
, and dynamicData
. This better reflects the on-chain state and makes it easier to perform modifications to the raw bytes. We recommend storing each of these fields individually in your off-chain storage of choice (indexer, client, etc.).
- event StoreSetRecord(bytes32 tableId, bytes32[] keyTuple, bytes data);
+ event StoreSetRecord(bytes32 tableId, bytes32[] keyTuple, bytes staticData, bytes32 encodedLengths, bytes dynamicData);
- event StoreEphemeralRecord(bytes32 tableId, bytes32[] keyTuple, bytes data);
+ event StoreEphemeralRecord(bytes32 tableId, bytes32[] keyTuple, bytes staticData, bytes32 encodedLengths, bytes dynamicData);
The StoreSetField
event is now replaced by two new events: StoreSpliceStaticData
and StoreSpliceDynamicData
. Splicing allows us to perform efficient operations like push and pop, in addition to replacing a field value. We use two events because updating a dynamic-length field also requires updating the record's encodedLengths
(aka PackedCounter).
- event StoreSetField(bytes32 tableId, bytes32[] keyTuple, uint8 fieldIndex, bytes data);
+ event StoreSpliceStaticData(bytes32 tableId, bytes32[] keyTuple, uint48 start, uint40 deleteCount, bytes data);
+ event StoreSpliceDynamicData(bytes32 tableId, bytes32[] keyTuple, uint48 start, uint40 deleteCount, bytes data, bytes32 encodedLengths);
Similarly, Store setter methods (e.g. setRecord
) have been updated to reflect the data
to staticData
, encodedLengths
, and dynamicData
changes. We'll be following up shortly with Store getter method changes for more gas efficient storage reads.
#1589 f9f9609e
Thanks @alvrs! - The argument order on Store_SpliceDynamicData
, onBeforeSpliceDynamicData
and onAfterSpliceDynamicData
has been changed to match the argument order on Store_SetRecord
,
where the PackedCounter encodedLength
field comes before the bytes dynamicData
field.
IStore {
event Store_SpliceDynamicData(
ResourceId indexed tableId,
bytes32[] keyTuple,
uint48 start,
uint40 deleteCount,
+ PackedCounter encodedLengths,
bytes data,
- PackedCounter encodedLengths
);
}
IStoreHook {
function onBeforeSpliceDynamicData(
ResourceId tableId,
bytes32[] memory keyTuple,
uint8 dynamicFieldIndex,
uint40 startWithinField,
uint40 deleteCount,
+ PackedCounter encodedLengths,
bytes memory data,
- PackedCounter encodedLengths
) external;
function onAfterSpliceDynamicData(
ResourceId tableId,
bytes32[] memory keyTuple,
uint8 dynamicFieldIndex,
uint40 startWithinField,
uint40 deleteCount,
+ PackedCounter encodedLengths,
bytes memory data,
- PackedCounter encodedLengths
) external;
}
#1527 759514d8
Thanks @holic! - Moved the registration of store hooks and systems hooks to bitmaps with bitwise operator instead of a struct.
- import { StoreHookLib } from "@latticexyz/src/StoreHook.sol";
+ import {
+ BEFORE_SET_RECORD,
+ BEFORE_SET_FIELD,
+ BEFORE_DELETE_RECORD
+ } from "@latticexyz/store/storeHookTypes.sol";
StoreCore.registerStoreHook(
tableId,
subscriber,
- StoreHookLib.encodeBitmap({
- onBeforeSetRecord: true,
- onAfterSetRecord: false,
- onBeforeSetField: true,
- onAfterSetField: false,
- onBeforeDeleteRecord: true,
- onAfterDeleteRecord: false
- })
+ BEFORE_SET_RECORD | BEFORE_SET_FIELD | BEFORE_DELETE_RECORD
);
- import { SystemHookLib } from "../src/SystemHook.sol";
+ import { BEFORE_CALL_SYSTEM, AFTER_CALL_SYSTEM } from "../src/systemHookTypes.sol";
world.registerSystemHook(
systemId,
subscriber,
- SystemHookLib.encodeBitmap({ onBeforeCallSystem: true, onAfterCallSystem: true })
+ BEFORE_CALL_SYSTEM | AFTER_CALL_SYSTEM
);
#1531 d5094a24
Thanks @alvrs! - - The IStoreHook
interface was changed to replace onBeforeSetField
and onAfterSetField
with onBeforeSpliceStaticData
, onAfterSpliceStaticData
, onBeforeSpliceDynamicData
and onAfterSpliceDynamicData
.
This new interface matches the new StoreSpliceStaticData
and StoreSpliceDynamicData
events, and avoids having to read the entire field from storage when only a subset of the field was updated
(e.g. when pushing elements to a field).
interface IStoreHook {
- function onBeforeSetField(
- bytes32 tableId,
- bytes32[] memory keyTuple,
- uint8 fieldIndex,
- bytes memory data,
- FieldLayout fieldLayout
- ) external;
- function onAfterSetField(
- bytes32 tableId,
- bytes32[] memory keyTuple,
- uint8 fieldIndex,
- bytes memory data,
- FieldLayout fieldLayout
- ) external;
+ function onBeforeSpliceStaticData(
+ bytes32 tableId,
+ bytes32[] memory keyTuple,
+ uint48 start,
+ uint40 deleteCount,
+ bytes memory data
+ ) external;
+ function onAfterSpliceStaticData(
+ bytes32 tableId,
+ bytes32[] memory keyTuple,
+ uint48 start,
+ uint40 deleteCount,
+ bytes memory data
+ ) external;
+ function onBeforeSpliceDynamicData(
+ bytes32 tableId,
+ bytes32[] memory keyTuple,
+ uint8 dynamicFieldIndex,
+ uint40 startWithinField,
+ uint40 deleteCount,
+ bytes memory data,
+ PackedCounter encodedLengths
+ ) external;
+ function onAfterSpliceDynamicData(
+ bytes32 tableId,
+ bytes32[] memory keyTuple,
+ uint8 dynamicFieldIndex,
+ uint40 startWithinField,
+ uint40 deleteCount,
+ bytes memory data,
+ PackedCounter encodedLengths
+ ) external;
}
All calldata
parameters on the IStoreHook
interface were changed to memory
, since the functions are called with memory
from the World
.
IStore
exposes two new functions: spliceStaticData
and spliceDynamicData
.
These functions provide lower level access to the operations happening under the hood in setField
, pushToField
, popFromField
and updateInField
and simplify handling
the new splice hooks.
StoreCore
's internal logic was simplified to use the spliceStaticData
and spliceDynamicData
functions instead of duplicating similar logic in different functions.
interface IStore {
// Splice data in the static part of the record
function spliceStaticData(
bytes32 tableId,
bytes32[] calldata keyTuple,
uint48 start,
uint40 deleteCount,
bytes calldata data
) external;
// Splice data in the dynamic part of the record
function spliceDynamicData(
bytes32 tableId,
bytes32[] calldata keyTuple,
uint8 dynamicFieldIndex,
uint40 startWithinField,
uint40 deleteCount,
bytes calldata data
) external;
}
#1336 de151fec
Thanks @dk1a! - - Add FieldLayout
, which is a bytes32
user-type similar to Schema
.
Both FieldLayout
and Schema
have the same kind of data in the first 4 bytes.
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
.
#1532 ae340b2b
Thanks @dk1a! - 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);
#1483 83583a50
Thanks @holic! - 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";
#1566 44a5432a
Thanks @dk1a! - 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.#1550 65c9546c
Thanks @dk1a! - - 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.#1602 672d05ca
Thanks @holic! - - 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.
#1473 92de5998
Thanks @holic! - Bump Solidity version to 0.8.21
#1318 ac508bf1
Thanks @holic! - Renamed the default filename of generated user types from Types.sol
to common.sol
and the default filename of the generated table index file from Tables.sol
to index.sol
.
Both can be overridden via the MUD config:
export default mudConfig({
/** Filename where common user types will be generated and imported from. */
userTypesFilename: "common.sol",
/** Filename where codegen index will be generated. */
codegenIndexFilename: "index.sol",
});
Note: userTypesFilename
was renamed from userTypesPath
and .sol
is not appended automatically anymore but needs to be part of the provided filename.
To update your existing project, update all imports from Tables.sol
to index.sol
and all imports from Types.sol
to common.sol
, or override the defaults in your MUD config to the previous values.
- import { Counter } from "../src/codegen/Tables.sol";
+ import { Counter } from "../src/codegen/index.sol";
- import { ExampleEnum } from "../src/codegen/Types.sol";
+ import { ExampleEnum } from "../src/codegen/common.sol";
#1558 bfcb293d
Thanks @alvrs! - What used to be known as ephemeral
table is now called offchain
table.
The previous ephemeral
tables only supported an emitEphemeral
method, which emitted a StoreSetEphemeralRecord
event.
Now offchain
tables support all regular table methods, except partial operations on dynamic fields (push
, pop
, update
).
Unlike regular tables they don't store data on-chain but emit the same events as regular tables (StoreSetRecord
, StoreSpliceStaticData
, StoreDeleteRecord
), so their data can be indexed by offchain indexers/clients.
- EphemeralTable.emitEphemeral(value);
+ OffchainTable.set(value);
#1601 1890f1a0
Thanks @alvrs! - Moved store
tables to the "store"
namespace (previously "mudstore") and world
tables to the "world"
namespace (previously root namespace).
#1577 af639a26
Thanks @alvrs! - Store
events have been renamed for consistency and readability.
If you're parsing Store
events manually, you need to update your ABI.
If you're using the MUD sync stack, the new events are already integrated and no further changes are necessary.
- event StoreSetRecord(
+ event Store_SetRecord(
ResourceId indexed tableId,
bytes32[] keyTuple,
bytes staticData,
bytes32 encodedLengths,
bytes dynamicData
);
- event StoreSpliceStaticData(
+ event Store_SpliceStaticData(
ResourceId indexed tableId,
bytes32[] keyTuple,
uint48 start,
uint40 deleteCount,
bytes data
);
- event StoreSpliceDynamicData(
+ event Store_SpliceDynamicData(
ResourceId indexed tableId,
bytes32[] keyTuple,
uint48 start,
uint40 deleteCount,
bytes data,
bytes32 encodedLengths
);
- event StoreDeleteRecord(
+ event Store_DeleteRecord(
ResourceId indexed tableId,
bytes32[] keyTuple
);
#1544 5e723b90
Thanks @alvrs! - - ResourceSelector
is replaced with ResourceId
, ResourceIdLib
, ResourceIdInstance
, WorldResourceIdLib
and WorldResourceIdInstance
.
Previously a "resource selector" was a bytes32
value with the first 16 bytes reserved for the resource's namespace, and the last 16 bytes reserved for the resource's name.
Now a "resource ID" is a bytes32
value with the first 2 bytes reserved for the resource type, the next 14 bytes reserved for the resource's namespace, and the last 16 bytes reserved for the resource's name.
Previously ResouceSelector
was a library and the resource selector type was a plain bytes32
.
Now ResourceId
is a user type, and the functionality is implemented in the ResourceIdInstance
(for type) and WorldResourceIdInstance
(for namespace and name) libraries.
We split the logic into two libraries, because Store
now also uses ResourceId
and needs to be aware of resource types, but not of namespaces/names.
- import { ResourceSelector } from "@latticexyz/world/src/ResourceSelector.sol";
+ import { ResourceId, ResourceIdInstance } from "@latticexyz/store/src/ResourceId.sol";
+ import { WorldResourceIdLib, WorldResourceIdInstance } from "@latticexyz/world/src/WorldResourceId.sol";
+ import { RESOURCE_SYSTEM } from "@latticexyz/world/src/worldResourceTypes.sol";
- bytes32 systemId = ResourceSelector.from("namespace", "name");
+ ResourceId systemId = WorldResourceIdLib.encode(RESOURCE_SYSTEM, "namespace", "name");
- using ResourceSelector for bytes32;
+ using WorldResourceIdInstance for ResourceId;
+ using ResourceIdInstance for ResourceId;
systemId.getName();
systemId.getNamespace();
+ systemId.getType();
All Store
and World
methods now use the ResourceId
type for tableId
, systemId
, moduleId
and namespaceId
.
All mentions of resourceSelector
were renamed to resourceId
or the more specific type (e.g. tableId
, systemId
)
import { ResourceId } from "@latticexyz/store/src/ResourceId.sol";
IStore {
function setRecord(
- bytes32 tableId,
+ ResourceId tableId,
bytes32[] calldata keyTuple,
bytes calldata staticData,
PackedCounter encodedLengths,
bytes calldata dynamicData,
FieldLayout fieldLayout
) external;
// Same for all other methods
}
import { ResourceId } from "@latticexyz/store/src/ResourceId.sol";
IBaseWorld {
function callFrom(
address delegator,
- bytes32 resourceSelector,
+ ResourceId systemId,
bytes memory callData
) external payable returns (bytes memory);
// Same for all other methods
}
#1520 99ab9cd6
Thanks @holic! - Store events now use an indexed
tableId
. This adds ~100 gas per write, but means we our sync stack can filter events by table.
#1472 c049c23f
Thanks @alvrs! - - 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.#1587 24a6cd53
Thanks @alvrs! - Changed the userTypes
property to accept { filePath: string, internalType: SchemaAbiType }
to enable strong type inference from the config.
#1581 cea754dd
Thanks @alvrs! - - The external setRecord
and deleteRecord
methods of IStore
no longer accept a FieldLayout
as input, but load it from storage instead.
This is to prevent invalid FieldLayout
values being passed, which could cause the onchain state to diverge from the indexer state.
However, the internal StoreCore
library still exposes a setRecord
and deleteRecord
method that allows a FieldLayout
to be passed.
This is because StoreCore
can only be used internally, so the FieldLayout
value can be trusted and we can save the gas for accessing storage.
interface IStore {
function setRecord(
ResourceId tableId,
bytes32[] calldata keyTuple,
bytes calldata staticData,
PackedCounter encodedLengths,
bytes calldata dynamicData,
- FieldLayout fieldLayout
) external;
function deleteRecord(
ResourceId tableId,
bytes32[] memory keyTuple,
- FieldLayout fieldLayout
) external;
}
The spliceStaticData
method and Store_SpliceStaticData
event of IStore
and StoreCore
no longer include deleteCount
in their signature.
This is because when splicing static data, the data after start
is always overwritten with data
instead of being shifted, so deleteCount
is always the length of the data to be written.
event Store_SpliceStaticData(
ResourceId indexed tableId,
bytes32[] keyTuple,
uint48 start,
- uint40 deleteCount,
bytes data
);
interface IStore {
function spliceStaticData(
ResourceId tableId,
bytes32[] calldata keyTuple,
uint48 start,
- uint40 deleteCount,
bytes calldata data
) external;
}
The updateInField
method has been removed from IStore
, as it's almost identical to the more general spliceDynamicData
.
If you're manually calling updateInField
, here is how to upgrade to spliceDynamicData
:
- store.updateInField(tableId, keyTuple, fieldIndex, startByteIndex, dataToSet, fieldLayout);
+ uint8 dynamicFieldIndex = fieldIndex - fieldLayout.numStaticFields();
+ store.spliceDynamicData(tableId, keyTuple, dynamicFieldIndex, uint40(startByteIndex), uint40(dataToSet.length), dataToSet);
All other methods that are only valid for dynamic fields (pushToField
, popFromField
, getFieldSlice
)
have been renamed to make this more explicit (pushToDynamicField
, popFromDynamicField
, getDynamicFieldSlice
).
Their fieldIndex
parameter has been replaced by a dynamicFieldIndex
parameter, which is the index relative to the first dynamic field (i.e. dynamicFieldIndex
= fieldIndex
- numStaticFields
).
The FieldLayout
parameter has been removed, as it was only used to calculate the dynamicFieldIndex
in the method.
interface IStore {
- function pushToField(
+ function pushToDynamicField(
ResourceId tableId,
bytes32[] calldata keyTuple,
- uint8 fieldIndex,
+ uint8 dynamicFieldIndex,
bytes calldata dataToPush,
- FieldLayout fieldLayout
) external;
- function popFromField(
+ function popFromDynamicField(
ResourceId tableId,
bytes32[] calldata keyTuple,
- uint8 fieldIndex,
+ uint8 dynamicFieldIndex,
uint256 byteLengthToPop,
- FieldLayout fieldLayout
) external;
- function getFieldSlice(
+ function getDynamicFieldSlice(
ResourceId tableId,
bytes32[] memory keyTuple,
- uint8 fieldIndex,
+ uint8 dynamicFieldIndex,
- FieldLayout fieldLayout,
uint256 start,
uint256 end
) external view returns (bytes memory data);
}
IStore
has a new getDynamicFieldLength
length method, which returns the byte length of the given dynamic field and doesn't require the FieldLayout
.
IStore {
+ function getDynamicFieldLength(
+ ResourceId tableId,
+ bytes32[] memory keyTuple,
+ uint8 dynamicFieldIndex
+ ) external view returns (uint256);
}
IStore
now has additional overloads for getRecord
, getField
, getFieldLength
and setField
that don't require a FieldLength
to be passed, but instead load it from storage.
IStore
now exposes setStaticField
and setDynamicField
to save gas by avoiding the dynamic inference of whether the field is static or dynamic.
The getDynamicFieldSlice
method no longer accepts reading outside the bounds of the dynamic field.
This is to avoid returning invalid data, as the data of a dynamic field is not deleted when the record is deleted, but only its length is set to zero.
#1511 9b43029c
Thanks @holic! - 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);
#1521 55ab88a6
Thanks @alvrs! - StoreCore
and IStore
now expose specific functions for getStaticField
and getDynamicField
in addition to the general getField
.
Using the specific functions reduces gas overhead because more optimized logic can be executed.
interface IStore {
/**
* Get a single static field from the given tableId and key tuple, with the given value field layout.
* Note: the field value is left-aligned in the returned bytes32, the rest of the word is not zeroed out.
* Consumers are expected to truncate the returned value as needed.
*/
function getStaticField(
bytes32 tableId,
bytes32[] calldata keyTuple,
uint8 fieldIndex,
FieldLayout fieldLayout
) external view returns (bytes32);
/**
* Get a single dynamic field from the given tableId and key tuple at the given dynamic field index.
* (Dynamic field index = field index - number of static fields)
*/
function getDynamicField(
bytes32 tableId,
bytes32[] memory keyTuple,
uint8 dynamicFieldIndex
) external view returns (bytes memory);
}
#1542 80dd6992
Thanks @dk1a! - Add an optional namePrefix
argument to renderRecordData
, to support inlined logic in codegenned set
method which uses a struct.
#1513 708b49c5
Thanks @Boffee! - Generated table libraries now have a set of functions prefixed with _
that always use their own storage for read/write.
This saves gas for use cases where the functionality to dynamically determine which Store
to use for read/write is not needed, e.g. root systems in a World
, or when using Store
without World
.
We decided to continue to always generate a set of functions that dynamically decide which Store
to use, so that the generated table libraries can still be imported by non-root systems.
library Counter {
// Dynamically determine which store to write to based on the context
function set(uint32 value) internal;
// Always write to own storage
function _set(uint32 value) internal;
// ... equivalent functions for all other Store methods
}
#1490 aea67c58
Thanks @alvrs! - Include bytecode for World
and Store
in npm packages.
#1600 90e4161b
Thanks @alvrs! - Moved the test tables out of the main config in world
and store
and into their own separate config.
#1508 211be2a1
Thanks @Boffee! - The FieldLayout
in table libraries is now generated at compile time instead of dynamically in a table library function.
This significantly reduces gas cost in all table library functions.
#1512 0f3e2e02
Thanks @Boffee! - Added Storage.loadField
to optimize loading 32 bytes or less from storage (which is always the case when loading data for static fields).
#1568 d0878928
Thanks @alvrs! - Prefixed all errors with their respective library/contract for improved debugging.
#1544 5e723b90
Thanks @alvrs! - 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);
#1484 6573e38e
Thanks @alvrs! - Renamed all occurrences of table
where it is used as "table ID" to tableId
.
This is only a breaking change for consumers who manually decode Store
events, but not for consumers who use the MUD libraries.
event StoreSetRecord(
- bytes32 table,
+ bytes32 tableId,
bytes32[] key,
bytes data
);
event StoreSetField(
- bytes32 table,
+ bytes32 tableId,
bytes32[] key,
uint8 fieldIndex,
bytes data
);
event StoreDeleteRecord(
- bytes32 table,
+ bytes32 tableId,
bytes32[] key
);
event StoreEphemeralRecord(
- bytes32 table,
+ bytes32 tableId,
bytes32[] key,
bytes data
);
#1492 6e66c5b7
Thanks @alvrs! - Renamed all occurrences of key
where it is used as "key tuple" to keyTuple
.
This is only a breaking change for consumers who manually decode Store
events, but not for consumers who use the MUD libraries.
event StoreSetRecord(
bytes32 tableId,
- bytes32[] key,
+ bytes32[] keyTuple,
bytes data
);
event StoreSetField(
bytes32 tableId,
- bytes32[] key,
+ bytes32[] keyTuple,
uint8 fieldIndex,
bytes data
);
event StoreDeleteRecord(
bytes32 tableId,
- bytes32[] key,
+ bytes32[] keyTuple,
);
event StoreEphemeralRecord(
bytes32 tableId,
- bytes32[] key,
+ bytes32[] keyTuple,
bytes data
);
#1599 63831a26
Thanks @alvrs! - Minor Store
cleanups: renamed Utils.sol
to leftMask.sol
since it only contains a single free function, and removed a leftover sanity check.
#1586 22ee4470
Thanks @alvrs! - All Store
and World
tables now use the appropriate user-types for ResourceId
, FieldLayout
and Schema
to avoid manual wrap
/unwrap
.
#1509 be313068
Thanks @Boffee! - Optimized the StoreCore
hash function determining the data location to use less gas.
#1569 22ba7b67
Thanks @alvrs! - Simplified a couple internal constants used for bitshifting.
Updated dependencies [65c9546c
, 331dbfdc
, 0b8ce3f2
, 44a5432a
, 331dbfdc
, 92de5998
, bfcb293d
, 5e723b90
, 24a6cd53
, 708b49c5
, c4f49240
, cea754dd
]:
Published by github-actions[bot] about 1 year ago
Published by github-actions[bot] about 1 year ago
Published by github-actions[bot] about 1 year ago
Published by github-actions[bot] about 1 year ago
Published by github-actions[bot] about 1 year ago
92de5998
]:
Published by github-actions[bot] about 1 year ago
aea67c58
, 07dd6f32
, 90e4161b
, 331dbfdc
, f9f9609e
, 759514d8
, d5094a24
, de151fec
, ae340b2b
, 211be2a1
, 0f3e2e02
, d0878928
, 83583a50
, 5e723b90
, 6573e38e
, 44a5432a
, 6e66c5b7
, 65c9546c
, 672d05ca
, 63831a26
, 92de5998
, 22ee4470
, be313068
, ac508bf1
, bfcb293d
, 1890f1a0
, 9b43029c
, 55ab88a6
, af639a26
, 5e723b90
, 99ab9cd6
, c049c23f
, 80dd6992
, 24a6cd53
, 708b49c5
, 22ba7b67
, cea754dd
]:
Published by github-actions[bot] about 1 year ago
#1482 07dd6f32
Thanks @alvrs! - Renamed all occurrences of schema
where it is used as "value schema" to valueSchema
to clearly distinguish it from "key schema".
The only breaking change for users is the change from schema
to valueSchema
in mud.config.ts
.
// mud.config.ts
export default mudConfig({
tables: {
CounterTable: {
keySchema: {},
- schema: {
+ valueSchema: {
value: "uint32",
},
},
}
}
#1354 331dbfdc
Thanks @dk1a! - readHex
was moved from @latticexyz/protocol-parser
to @latticexyz/common
#1336 de151fec
Thanks @dk1a! - - Add FieldLayout
, which is a bytes32
user-type similar to Schema
.
Both FieldLayout
and Schema
have the same kind of data in the first 4 bytes.
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
.
#1476 9ff4dd95
Thanks @holic! - Adds valueSchemaToFieldLayoutHex
helper
Published by github-actions[bot] about 1 year ago
Published by github-actions[bot] about 1 year ago
Published by github-actions[bot] about 1 year ago
Published by github-actions[bot] about 1 year ago
#1517 9940fdb3
Thanks @holic! - New package to run your own faucet service. We'll use this soon for our testnet in place of @latticexyz/services
.
To run the faucet server:
pnpm add @latticexyz/faucet
.env
file that has a RPC_HTTP_URL
and FAUCET_PRIVATE_KEY
(or pass the environment variables into the next command)pnpm faucet-server
to start the serverYou can also adjust the server's HOST
(defaults to 0.0.0.0
) and PORT
(defaults to 3002
). The tRPC routes are accessible under /trpc
.
To connect a tRPC client, add the package with pnpm add @latticexyz/faucet
and then use createClient
:
import { createClient } from "@latticexyz/faucet";
const faucet = createClient({ url: "http://localhost:3002/trpc" });
await faucet.mutate.drip({ address: burnerAccount.address });
Published by github-actions[bot] about 1 year ago
#1354 331dbfdc
Thanks @dk1a! - We've updated Store events to be "schemaless", meaning there is enough information in each event to only need to operate on the bytes of each record to make an update to that record without having to first decode the record by its schema. This enables new kinds of indexers and sync strategies.
As such, we've replaced blockStorageOperations# @latticexyz/dev-tools with
storedBlockLogs# @latticexyz/dev-tools, 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.
24a0dd44
Thanks @y77cao! - Improved rendering of transactions that make calls via World's call
and callFrom
methods#1505 e0193e57
Thanks @holic! - Updates store event key
reference to keyTuple
#1502 e0377761
Thanks @holic! - Updates table
reference to tableId
#1558 bfcb293d
Thanks @alvrs! - What used to be known as ephemeral
table is now called offchain
table.
The previous ephemeral
tables only supported an emitEphemeral
method, which emitted a StoreSetEphemeralRecord
event.
Now offchain
tables support all regular table methods, except partial operations on dynamic fields (push
, pop
, update
).
Unlike regular tables they don't store data on-chain but emit the same events as regular tables (StoreSetRecord
, StoreSpliceStaticData
, StoreDeleteRecord
), so their data can be indexed by offchain indexers/clients.
- EphemeralTable.emitEphemeral(value);
+ OffchainTable.set(value);
#1577 af639a26
Thanks @alvrs! - Store
events have been renamed for consistency and readability.
If you're parsing Store
events manually, you need to update your ABI.
If you're using the MUD sync stack, the new events are already integrated and no further changes are necessary.
- event StoreSetRecord(
+ event Store_SetRecord(
ResourceId indexed tableId,
bytes32[] keyTuple,
bytes staticData,
bytes32 encodedLengths,
bytes dynamicData
);
- event StoreSpliceStaticData(
+ event Store_SpliceStaticData(
ResourceId indexed tableId,
bytes32[] keyTuple,
uint48 start,
uint40 deleteCount,
bytes data
);
- event StoreSpliceDynamicData(
+ event Store_SpliceDynamicData(
ResourceId indexed tableId,
bytes32[] keyTuple,
uint48 start,
uint40 deleteCount,
bytes data,
bytes32 encodedLengths
);
- event StoreDeleteRecord(
+ event Store_DeleteRecord(
ResourceId indexed tableId,
bytes32[] keyTuple
);
#1581 cea754dd
Thanks @alvrs! - - The external setRecord
and deleteRecord
methods of IStore
no longer accept a FieldLayout
as input, but load it from storage instead.
This is to prevent invalid FieldLayout
values being passed, which could cause the onchain state to diverge from the indexer state.
However, the internal StoreCore
library still exposes a setRecord
and deleteRecord
method that allows a FieldLayout
to be passed.
This is because StoreCore
can only be used internally, so the FieldLayout
value can be trusted and we can save the gas for accessing storage.
interface IStore {
function setRecord(
ResourceId tableId,
bytes32[] calldata keyTuple,
bytes calldata staticData,
PackedCounter encodedLengths,
bytes calldata dynamicData,
- FieldLayout fieldLayout
) external;
function deleteRecord(
ResourceId tableId,
bytes32[] memory keyTuple,
- FieldLayout fieldLayout
) external;
}
The spliceStaticData
method and Store_SpliceStaticData
event of IStore
and StoreCore
no longer include deleteCount
in their signature.
This is because when splicing static data, the data after start
is always overwritten with data
instead of being shifted, so deleteCount
is always the length of the data to be written.
event Store_SpliceStaticData(
ResourceId indexed tableId,
bytes32[] keyTuple,
uint48 start,
- uint40 deleteCount,
bytes data
);
interface IStore {
function spliceStaticData(
ResourceId tableId,
bytes32[] calldata keyTuple,
uint48 start,
- uint40 deleteCount,
bytes calldata data
) external;
}
The updateInField
method has been removed from IStore
, as it's almost identical to the more general spliceDynamicData
.
If you're manually calling updateInField
, here is how to upgrade to spliceDynamicData
:
- store.updateInField(tableId, keyTuple, fieldIndex, startByteIndex, dataToSet, fieldLayout);
+ uint8 dynamicFieldIndex = fieldIndex - fieldLayout.numStaticFields();
+ store.spliceDynamicData(tableId, keyTuple, dynamicFieldIndex, uint40(startByteIndex), uint40(dataToSet.length), dataToSet);
All other methods that are only valid for dynamic fields (pushToField
, popFromField
, getFieldSlice
)
have been renamed to make this more explicit (pushToDynamicField
, popFromDynamicField
, getDynamicFieldSlice
).
Their fieldIndex
parameter has been replaced by a dynamicFieldIndex
parameter, which is the index relative to the first dynamic field (i.e. dynamicFieldIndex
= fieldIndex
- numStaticFields
).
The FieldLayout
parameter has been removed, as it was only used to calculate the dynamicFieldIndex
in the method.
interface IStore {
- function pushToField(
+ function pushToDynamicField(
ResourceId tableId,
bytes32[] calldata keyTuple,
- uint8 fieldIndex,
+ uint8 dynamicFieldIndex,
bytes calldata dataToPush,
- FieldLayout fieldLayout
) external;
- function popFromField(
+ function popFromDynamicField(
ResourceId tableId,
bytes32[] calldata keyTuple,
- uint8 fieldIndex,
+ uint8 dynamicFieldIndex,
uint256 byteLengthToPop,
- FieldLayout fieldLayout
) external;
- function getFieldSlice(
+ function getDynamicFieldSlice(
ResourceId tableId,
bytes32[] memory keyTuple,
- uint8 fieldIndex,
+ uint8 dynamicFieldIndex,
- FieldLayout fieldLayout,
uint256 start,
uint256 end
) external view returns (bytes memory data);
}
IStore
has a new getDynamicFieldLength
length method, which returns the byte length of the given dynamic field and doesn't require the FieldLayout
.
IStore {
+ function getDynamicFieldLength(
+ ResourceId tableId,
+ bytes32[] memory keyTuple,
+ uint8 dynamicFieldIndex
+ ) external view returns (uint256);
}
IStore
now has additional overloads for getRecord
, getField
, getFieldLength
and setField
that don't require a FieldLength
to be passed, but instead load it from storage.
IStore
now exposes setStaticField
and setDynamicField
to save gas by avoiding the dynamic inference of whether the field is static or dynamic.
The getDynamicFieldSlice
method no longer accepts reading outside the bounds of the dynamic field.
This is to avoid returning invalid data, as the data of a dynamic field is not deleted when the record is deleted, but only its length is set to zero.
Updated dependencies [77dce993
, 748f4588
, aea67c58
, 07dd6f32
, c07fa021
, 90e4161b
, 65c9546c
, 331dbfdc
, f9f9609e
, 331dbfdc
, 759514d8
, d5094a24
, 0b8ce3f2
, de151fec
, ae340b2b
, e5d208e4
, 211be2a1
, 0f3e2e02
, 1f80a0b5
, d0878928
, 4c7fd3eb
, a0341daf
, 83583a50
, 5e723b90
, 6573e38e
, 44a5432a
, 6e66c5b7
, 65c9546c
, 44a5432a
, 672d05ca
, f1cd43bf
, 7e6e5157
, 31ffc9d5
, 5e723b90
, 63831a26
, 331dbfdc
, 92de5998
, 5741d53d
, 22ee4470
, be313068
, ac508bf1
, 331dbfdc
, bfcb293d
, 1890f1a0
, 9b43029c
, 55ab88a6
, af639a26
, 5e723b90
, 99ab9cd6
, c049c23f
, 80dd6992
, 24a6cd53
, 708b49c5
, 22ba7b67
, c049c23f
, 251170e1
, c4f49240
, cea754dd
, 95c59b20
]:
Published by github-actions[bot] about 1 year ago
#1482 07dd6f32
Thanks @alvrs! - Renamed all occurrences of schema
where it is used as "value schema" to valueSchema
to clearly distinguish it from "key schema".
The only breaking change for users is the change from schema
to valueSchema
in mud.config.ts
.
// mud.config.ts
export default mudConfig({
tables: {
CounterTable: {
keySchema: {},
- schema: {
+ valueSchema: {
value: "uint32",
},
},
}
}
#1483 83583a50
Thanks @holic! - Templates now use out
for their forge build
artifacts, including ABIs. If you have a project created from a previous template, you can update your packages/contracts/package.json
with:
- "build:abi": "rimraf abi && forge build --extra-output-files abi --out abi --skip test script MudTest.sol",
- "build:abi-ts": "mud abi-ts --input 'abi/IWorld.sol/IWorld.abi.json' && prettier --write '**/*.abi.json.d.ts'",
+ "build:abi": "forge clean && forge build --skip test script",
+ "build:abi-ts": "mud abi-ts && prettier --write '**/*.abi.json.d.ts'",
And your packages/client/src/mud/setupNetwork
with:
- import IWorldAbi from "contracts/abi/IWorld.sol/IWorld.abi.json";
+ import IWorldAbi from "contracts/out/IWorld.sol/IWorld.abi.json";
#1473 92de5998
Thanks @holic! - Bump Solidity version to 0.8.21
#1354 331dbfdc
Thanks @dk1a! - We've updated Store events to be "schemaless", meaning there is enough information in each event to only need to operate on the bytes of each record to make an update to that record without having to first decode the record by its schema. This enables new kinds of indexers and sync strategies.
As such, we've replaced blockStorageOperations# Change Log with
storedBlockLogs# Change Log, a stream of simplified Store event logs after they've been synced to the configured storage adapter. These logs may not reflect exactly the events that are on chain when e.g. hydrating from an indexer, but they will still allow the client to "catch up" to the on-chain state of your tables.
#1558 bfcb293d
Thanks @alvrs! - What used to be known as ephemeral
table is now called offchain
table.
The previous ephemeral
tables only supported an emitEphemeral
method, which emitted a StoreSetEphemeralRecord
event.
Now offchain
tables support all regular table methods, except partial operations on dynamic fields (push
, pop
, update
).
Unlike regular tables they don't store data on-chain but emit the same events as regular tables (StoreSetRecord
, StoreSpliceStaticData
, StoreDeleteRecord
), so their data can be indexed by offchain indexers/clients.
- EphemeralTable.emitEphemeral(value);
+ OffchainTable.set(value);
#1318 ac508bf1
Thanks @holic! - Renamed the default filename of generated user types from Types.sol
to common.sol
and the default filename of the generated table index file from Tables.sol
to index.sol
.
Both can be overridden via the MUD config:
export default mudConfig({
/** Filename where common user types will be generated and imported from. */
userTypesFilename: "common.sol",
/** Filename where codegen index will be generated. */
codegenIndexFilename: "index.sol",
});
Note: userTypesFilename
was renamed from userTypesPath
and .sol
is not appended automatically anymore but needs to be part of the provided filename.
To update your existing project, update all imports from Tables.sol
to index.sol
and all imports from Types.sol
to common.sol
, or override the defaults in your MUD config to the previous values.
- import { Counter } from "../src/codegen/Tables.sol";
+ import { Counter } from "../src/codegen/index.sol";
- import { ExampleEnum } from "../src/codegen/Types.sol";
+ import { ExampleEnum } from "../src/codegen/common.sol";
#1581 cea754dd
Thanks @alvrs! - - The external setRecord
and deleteRecord
methods of IStore
no longer accept a FieldLayout
as input, but load it from storage instead.
This is to prevent invalid FieldLayout
values being passed, which could cause the onchain state to diverge from the indexer state.
However, the internal StoreCore
library still exposes a setRecord
and deleteRecord
method that allows a FieldLayout
to be passed.
This is because StoreCore
can only be used internally, so the FieldLayout
value can be trusted and we can save the gas for accessing storage.
interface IStore {
function setRecord(
ResourceId tableId,
bytes32[] calldata keyTuple,
bytes calldata staticData,
PackedCounter encodedLengths,
bytes calldata dynamicData,
- FieldLayout fieldLayout
) external;
function deleteRecord(
ResourceId tableId,
bytes32[] memory keyTuple,
- FieldLayout fieldLayout
) external;
}
The spliceStaticData
method and Store_SpliceStaticData
event of IStore
and StoreCore
no longer include deleteCount
in their signature.
This is because when splicing static data, the data after start
is always overwritten with data
instead of being shifted, so deleteCount
is always the length of the data to be written.
event Store_SpliceStaticData(
ResourceId indexed tableId,
bytes32[] keyTuple,
uint48 start,
- uint40 deleteCount,
bytes data
);
interface IStore {
function spliceStaticData(
ResourceId tableId,
bytes32[] calldata keyTuple,
uint48 start,
- uint40 deleteCount,
bytes calldata data
) external;
}
The updateInField
method has been removed from IStore
, as it's almost identical to the more general spliceDynamicData
.
If you're manually calling updateInField
, here is how to upgrade to spliceDynamicData
:
- store.updateInField(tableId, keyTuple, fieldIndex, startByteIndex, dataToSet, fieldLayout);
+ uint8 dynamicFieldIndex = fieldIndex - fieldLayout.numStaticFields();
+ store.spliceDynamicData(tableId, keyTuple, dynamicFieldIndex, uint40(startByteIndex), uint40(dataToSet.length), dataToSet);
All other methods that are only valid for dynamic fields (pushToField
, popFromField
, getFieldSlice
)
have been renamed to make this more explicit (pushToDynamicField
, popFromDynamicField
, getDynamicFieldSlice
).
Their fieldIndex
parameter has been replaced by a dynamicFieldIndex
parameter, which is the index relative to the first dynamic field (i.e. dynamicFieldIndex
= fieldIndex
- numStaticFields
).
The FieldLayout
parameter has been removed, as it was only used to calculate the dynamicFieldIndex
in the method.
interface IStore {
- function pushToField(
+ function pushToDynamicField(
ResourceId tableId,
bytes32[] calldata keyTuple,
- uint8 fieldIndex,
+ uint8 dynamicFieldIndex,
bytes calldata dataToPush,
- FieldLayout fieldLayout
) external;
- function popFromField(
+ function popFromDynamicField(
ResourceId tableId,
bytes32[] calldata keyTuple,
- uint8 fieldIndex,
+ uint8 dynamicFieldIndex,
uint256 byteLengthToPop,
- FieldLayout fieldLayout
) external;
- function getFieldSlice(
+ function getDynamicFieldSlice(
ResourceId tableId,
bytes32[] memory keyTuple,
- uint8 fieldIndex,
+ uint8 dynamicFieldIndex,
- FieldLayout fieldLayout,
uint256 start,
uint256 end
) external view returns (bytes memory data);
}
IStore
has a new getDynamicFieldLength
length method, which returns the byte length of the given dynamic field and doesn't require the FieldLayout
.
IStore {
+ function getDynamicFieldLength(
+ ResourceId tableId,
+ bytes32[] memory keyTuple,
+ uint8 dynamicFieldIndex
+ ) external view returns (uint256);
}
IStore
now has additional overloads for getRecord
, getField
, getFieldLength
and setField
that don't require a FieldLength
to be passed, but instead load it from storage.
IStore
now exposes setStaticField
and setDynamicField
to save gas by avoiding the dynamic 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.