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
#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
}
Published by github-actions[bot] about 1 year ago
#1550 65c9546c
Thanks @dk1a! - - Add renderWithFieldSuffix
helper method to always render a field function with a suffix, and optionally render the same function without a suffix.
methodNameSuffix
from RenderField
interface, because the suffix is now computed as part of renderWithFieldSuffix
.#1558 bfcb293d
Thanks @alvrs! - What used to be known as ephemeral
table is now called offchain
table.
The previous ephemeral
tables only supported an emitEphemeral
method, which emitted a StoreSetEphemeralRecord
event.
Now offchain
tables support all regular table methods, except partial operations on dynamic fields (push
, pop
, update
).
Unlike regular tables they don't store data on-chain but emit the same events as regular tables (StoreSetRecord
, StoreSpliceStaticData
, StoreDeleteRecord
), so their data can be indexed by offchain indexers/clients.
- EphemeralTable.emitEphemeral(value);
+ OffchainTable.set(value);
#1544 5e723b90
Thanks @alvrs! - - ResourceSelector
is replaced with ResourceId
, ResourceIdLib
, ResourceIdInstance
, WorldResourceIdLib
and WorldResourceIdInstance
.
Previously a "resource selector" was a bytes32
value with the first 16 bytes reserved for the resource's namespace, and the last 16 bytes reserved for the resource's name.
Now a "resource ID" is a bytes32
value with the first 2 bytes reserved for the resource type, the next 14 bytes reserved for the resource's namespace, and the last 16 bytes reserved for the resource's name.
Previously ResouceSelector
was a library and the resource selector type was a plain bytes32
.
Now ResourceId
is a user type, and the functionality is implemented in the ResourceIdInstance
(for type) and WorldResourceIdInstance
(for namespace and name) libraries.
We split the logic into two libraries, because Store
now also uses ResourceId
and needs to be aware of resource types, but not of namespaces/names.
- import { ResourceSelector } from "@latticexyz/world/src/ResourceSelector.sol";
+ import { ResourceId, ResourceIdInstance } from "@latticexyz/store/src/ResourceId.sol";
+ import { WorldResourceIdLib, WorldResourceIdInstance } from "@latticexyz/world/src/WorldResourceId.sol";
+ import { RESOURCE_SYSTEM } from "@latticexyz/world/src/worldResourceTypes.sol";
- bytes32 systemId = ResourceSelector.from("namespace", "name");
+ ResourceId systemId = WorldResourceIdLib.encode(RESOURCE_SYSTEM, "namespace", "name");
- using ResourceSelector for bytes32;
+ using WorldResourceIdInstance for ResourceId;
+ using ResourceIdInstance for ResourceId;
systemId.getName();
systemId.getNamespace();
+ systemId.getType();
All Store
and World
methods now use the ResourceId
type for tableId
, systemId
, moduleId
and namespaceId
.
All mentions of resourceSelector
were renamed to resourceId
or the more specific type (e.g. tableId
, systemId
)
import { ResourceId } from "@latticexyz/store/src/ResourceId.sol";
IStore {
function setRecord(
- bytes32 tableId,
+ ResourceId tableId,
bytes32[] calldata keyTuple,
bytes calldata staticData,
PackedCounter encodedLengths,
bytes calldata dynamicData,
FieldLayout fieldLayout
) external;
// Same for all other methods
}
import { ResourceId } from "@latticexyz/store/src/ResourceId.sol";
IBaseWorld {
function callFrom(
address delegator,
- bytes32 resourceSelector,
+ ResourceId systemId,
bytes memory callData
) external payable returns (bytes memory);
// Same for all other methods
}
#1354 331dbfdc
Thanks @dk1a! - readHex
was moved from @latticexyz/protocol-parser
to @latticexyz/common
#1566 44a5432a
Thanks @dk1a! - - Add getRemappings
to get foundry remappings as an array of [to, from]
tuples.
extractUserTypes
solidity parser utility to extract user-defined types.loadAndExtractUserTypes
helper to load and parse a solidity file, extracting user-defined types.#1354 331dbfdc
Thanks @dk1a! - spliceHex
was added, which has a similar API as JavaScript's Array.prototype.splice
, but for Hex
strings.
spliceHex("0x123456", 1, 1, "0x0000"); // "0x12000056"
#1473 92de5998
Thanks @holic! - Bump Solidity version to 0.8.21
#1513 708b49c5
Thanks @Boffee! - Generated table libraries now have a set of functions prefixed with _
that always use their own storage for read/write.
This saves gas for use cases where the functionality to dynamically determine which Store
to use for read/write is not needed, e.g. root systems in a World
, or when using Store
without World
.
We decided to continue to always generate a set of functions that dynamically decide which Store
to use, so that the generated table libraries can still be imported by non-root systems.
library Counter {
// Dynamically determine which store to write to based on the context
function set(uint32 value) internal;
// Always write to own storage
function _set(uint32 value) internal;
// ... equivalent functions for all other Store methods
}
#1581 cea754dd
Thanks @alvrs! - - The external setRecord
and deleteRecord
methods of IStore
no longer accept a FieldLayout
as input, but load it from storage instead.
This is to prevent invalid FieldLayout
values being passed, which could cause the onchain state to diverge from the indexer state.
However, the internal StoreCore
library still exposes a setRecord
and deleteRecord
method that allows a FieldLayout
to be passed.
This is because StoreCore
can only be used internally, so the FieldLayout
value can be trusted and we can save the gas for accessing storage.
interface IStore {
function setRecord(
ResourceId tableId,
bytes32[] calldata keyTuple,
bytes calldata staticData,
PackedCounter encodedLengths,
bytes calldata dynamicData,
- FieldLayout fieldLayout
) external;
function deleteRecord(
ResourceId tableId,
bytes32[] memory keyTuple,
- FieldLayout fieldLayout
) external;
}
The spliceStaticData
method and Store_SpliceStaticData
event of IStore
and StoreCore
no longer include deleteCount
in their signature.
This is because when splicing static data, the data after start
is always overwritten with data
instead of being shifted, so deleteCount
is always the length of the data to be written.
event Store_SpliceStaticData(
ResourceId indexed tableId,
bytes32[] keyTuple,
uint48 start,
- uint40 deleteCount,
bytes data
);
interface IStore {
function spliceStaticData(
ResourceId tableId,
bytes32[] calldata keyTuple,
uint48 start,
- uint40 deleteCount,
bytes calldata data
) external;
}
The updateInField
method has been removed from IStore
, as it's almost identical to the more general spliceDynamicData
.
If you're manually calling updateInField
, here is how to upgrade to spliceDynamicData
:
- store.updateInField(tableId, keyTuple, fieldIndex, startByteIndex, dataToSet, fieldLayout);
+ uint8 dynamicFieldIndex = fieldIndex - fieldLayout.numStaticFields();
+ store.spliceDynamicData(tableId, keyTuple, dynamicFieldIndex, uint40(startByteIndex), uint40(dataToSet.length), dataToSet);
All other methods that are only valid for dynamic fields (pushToField
, popFromField
, getFieldSlice
)
have been renamed to make this more explicit (pushToDynamicField
, popFromDynamicField
, getDynamicFieldSlice
).
Their fieldIndex
parameter has been replaced by a dynamicFieldIndex
parameter, which is the index relative to the first dynamic field (i.e. dynamicFieldIndex
= fieldIndex
- numStaticFields
).
The FieldLayout
parameter has been removed, as it was only used to calculate the dynamicFieldIndex
in the method.
interface IStore {
- function pushToField(
+ function pushToDynamicField(
ResourceId tableId,
bytes32[] calldata keyTuple,
- uint8 fieldIndex,
+ uint8 dynamicFieldIndex,
bytes calldata dataToPush,
- FieldLayout fieldLayout
) external;
- function popFromField(
+ function popFromDynamicField(
ResourceId tableId,
bytes32[] calldata keyTuple,
- uint8 fieldIndex,
+ uint8 dynamicFieldIndex,
uint256 byteLengthToPop,
- FieldLayout fieldLayout
) external;
- function getFieldSlice(
+ function getDynamicFieldSlice(
ResourceId tableId,
bytes32[] memory keyTuple,
- uint8 fieldIndex,
+ uint8 dynamicFieldIndex,
- FieldLayout fieldLayout,
uint256 start,
uint256 end
) external view returns (bytes memory data);
}
IStore
has a new getDynamicFieldLength
length method, which returns the byte length of the given dynamic field and doesn't require the FieldLayout
.
IStore {
+ function getDynamicFieldLength(
+ ResourceId tableId,
+ bytes32[] memory keyTuple,
+ uint8 dynamicFieldIndex
+ ) external view returns (uint256);
}
IStore
now has additional overloads for getRecord
, getField
, getFieldLength
and setField
that don't require a FieldLength
to be passed, but instead load it from storage.
IStore
now exposes setStaticField
and setDynamicField
to save gas by avoiding the dynamic inference of whether the field is static or dynamic.
The getDynamicFieldSlice
method no longer accepts reading outside the bounds of the dynamic field.
This is to avoid returning invalid data, as the data of a dynamic field is not deleted when the record is deleted, but only its length is set to zero.
#1585 0b8ce3f2
Thanks @alvrs! - Minor fix to resolving user types: solc
doesn't like relative imports without ./
, but is fine with relative imports from ./../
, so we always append ./
to the relative path.
#1587 24a6cd53
Thanks @alvrs! - Changed the userTypes
property to accept { filePath: string, internalType: SchemaAbiType }
to enable strong type inference from the config.
#1598 c4f49240
Thanks @dk1a! - Table libraries now correctly handle uninitialized fixed length arrays.
Updated dependencies [92de5998
]:
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",
},
},
}
}
#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
.
#1575 e5d208e4
Thanks @alvrs! - The registerRootFunctionSelector
function's signature was changed to accept a string functionSignature
parameter instead of a bytes4 functionSelector
parameter.
This change enables the World
to store the function signatures of all registered functions in a FunctionSignatures
offchain table, which will allow for the automatic generation of interfaces for a given World
address in the future.
IBaseWorld {
function registerRootFunctionSelector(
ResourceId systemId,
- bytes4 worldFunctionSelector,
+ string memory worldFunctionSignature,
bytes4 systemFunctionSelector
) external returns (bytes4 worldFunctionSelector);
}
#1574 31ffc9d5
Thanks @alvrs! - The registerFunctionSelector
function now accepts a single functionSignature
string paramemer instead of separating function name and function arguments into separate parameters.
IBaseWorld {
function registerFunctionSelector(
ResourceId systemId,
- string memory systemFunctionName,
- string memory systemFunctionArguments
+ string memory systemFunctionSignature
) external returns (bytes4 worldFunctionSelector);
}
This is a breaking change if you were manually registering function selectors, e.g. in a PostDeploy.s.sol
script or a module.
To upgrade, simply replace the separate systemFunctionName
and systemFunctionArguments
parameters with a single systemFunctionSignature
parameter.
world.registerFunctionSelector(
systemId,
- systemFunctionName,
- systemFunctionArguments,
+ string(abi.encodePacked(systemFunctionName, systemFunctionArguments))
);
#1318 ac508bf1
Thanks @holic! - Renamed the default filename of generated user types from Types.sol
to common.sol
and the default filename of the generated table index file from Tables.sol
to index.sol
.
Both can be overridden via the MUD config:
export default mudConfig({
/** Filename where common user types will be generated and imported from. */
userTypesFilename: "common.sol",
/** Filename where codegen index will be generated. */
codegenIndexFilename: "index.sol",
});
Note: userTypesFilename
was renamed from userTypesPath
and .sol
is not appended automatically anymore but needs to be part of the provided filename.
To update your existing project, update all imports from Tables.sol
to index.sol
and all imports from Types.sol
to common.sol
, or override the defaults in your MUD config to the previous values.
- import { Counter } from "../src/codegen/Tables.sol";
+ import { Counter } from "../src/codegen/index.sol";
- import { ExampleEnum } from "../src/codegen/Types.sol";
+ import { ExampleEnum } from "../src/codegen/common.sol";
#1544 5e723b90
Thanks @alvrs! - - ResourceSelector
is replaced with ResourceId
, ResourceIdLib
, ResourceIdInstance
, WorldResourceIdLib
and WorldResourceIdInstance
.
Previously a "resource selector" was a bytes32
value with the first 16 bytes reserved for the resource's namespace, and the last 16 bytes reserved for the resource's name.
Now a "resource ID" is a bytes32
value with the first 2 bytes reserved for the resource type, the next 14 bytes reserved for the resource's namespace, and the last 16 bytes reserved for the resource's name.
Previously ResouceSelector
was a library and the resource selector type was a plain bytes32
.
Now ResourceId
is a user type, and the functionality is implemented in the ResourceIdInstance
(for type) and WorldResourceIdInstance
(for namespace and name) libraries.
We split the logic into two libraries, because Store
now also uses ResourceId
and needs to be aware of resource types, but not of namespaces/names.
- import { ResourceSelector } from "@latticexyz/world/src/ResourceSelector.sol";
+ import { ResourceId, ResourceIdInstance } from "@latticexyz/store/src/ResourceId.sol";
+ import { WorldResourceIdLib, WorldResourceIdInstance } from "@latticexyz/world/src/WorldResourceId.sol";
+ import { RESOURCE_SYSTEM } from "@latticexyz/world/src/worldResourceTypes.sol";
- bytes32 systemId = ResourceSelector.from("namespace", "name");
+ ResourceId systemId = WorldResourceIdLib.encode(RESOURCE_SYSTEM, "namespace", "name");
- using ResourceSelector for bytes32;
+ using WorldResourceIdInstance for ResourceId;
+ using ResourceIdInstance for ResourceId;
systemId.getName();
systemId.getNamespace();
+ systemId.getType();
All Store
and World
methods now use the ResourceId
type for tableId
, systemId
, moduleId
and namespaceId
.
All mentions of resourceSelector
were renamed to resourceId
or the more specific type (e.g. tableId
, systemId
)
import { ResourceId } from "@latticexyz/store/src/ResourceId.sol";
IStore {
function setRecord(
- bytes32 tableId,
+ ResourceId tableId,
bytes32[] calldata keyTuple,
bytes calldata staticData,
PackedCounter encodedLengths,
bytes calldata dynamicData,
FieldLayout fieldLayout
) external;
// Same for all other methods
}
import { ResourceId } from "@latticexyz/store/src/ResourceId.sol";
IBaseWorld {
function callFrom(
address delegator,
- bytes32 resourceSelector,
+ ResourceId systemId,
bytes memory callData
) external payable returns (bytes memory);
// Same for all other methods
}
#1490 aea67c58
Thanks @alvrs! - Include bytecode for World
and Store
in npm packages.
#1592 c07fa021
Thanks @alvrs! - Tables and interfaces in the world
package are now generated to the codegen
folder.
This is only a breaking change if you imported tables or codegenerated interfaces from @latticexyz/world
directly.
If you're using the MUD CLI, the changed import paths are already integrated and no further changes are necessary.
- import { IBaseWorld } from "@latticexyz/world/src/interfaces/IBaseWorld.sol";
+ import { IBaseWorld } from "@latticexyz/world/src/codegen/interfaces/IBaseWorld.sol";
#1508 211be2a1
Thanks @Boffee! - The FieldLayout
in table libraries is now generated at compile time instead of dynamically in a table library function.
This significantly reduces gas cost in all table library functions.
#1503 bd9cc8ec
Thanks @holic! - Refactor deploy
command to break up logic into modules
#1558 bfcb293d
Thanks @alvrs! - What used to be known as ephemeral
table is now called offchain
table.
The previous ephemeral
tables only supported an emitEphemeral
method, which emitted a StoreSetEphemeralRecord
event.
Now offchain
tables support all regular table methods, except partial operations on dynamic fields (push
, pop
, update
).
Unlike regular tables they don't store data on-chain but emit the same events as regular tables (StoreSetRecord
, StoreSpliceStaticData
, StoreDeleteRecord
), so their data can be indexed by offchain indexers/clients.
- EphemeralTable.emitEphemeral(value);
+ OffchainTable.set(value);
#1521 55ab88a6
Thanks @alvrs! - StoreCore
and IStore
now expose specific functions for getStaticField
and getDynamicField
in addition to the general getField
.
Using the specific functions reduces gas overhead because more optimized logic can be executed.
interface IStore {
/**
* Get a single static field from the given tableId and key tuple, with the given value field layout.
* Note: the field value is left-aligned in the returned bytes32, the rest of the word is not zeroed out.
* Consumers are expected to truncate the returned value as needed.
*/
function getStaticField(
bytes32 tableId,
bytes32[] calldata keyTuple,
uint8 fieldIndex,
FieldLayout fieldLayout
) external view returns (bytes32);
/**
* Get a single dynamic field from the given tableId and key tuple at the given dynamic field index.
* (Dynamic field index = field index - number of static fields)
*/
function getDynamicField(
bytes32 tableId,
bytes32[] memory keyTuple,
uint8 dynamicFieldIndex
) external view returns (bytes memory);
}
#1483 83583a50
Thanks @holic! - deploy
and dev-contracts
CLI commands now use forge build --skip test script
before deploying and run mud abi-ts
to generate strong types for ABIs.
#1587 24a6cd53
Thanks @alvrs! - Changed the userTypes
property to accept { filePath: string, internalType: SchemaAbiType }
to enable strong type inference from the config.
#1513 708b49c5
Thanks @Boffee! - Generated table libraries now have a set of functions prefixed with _
that always use their own storage for read/write.
This saves gas for use cases where the functionality to dynamically determine which Store
to use for read/write is not needed, e.g. root systems in a World
, or when using Store
without World
.
We decided to continue to always generate a set of functions that dynamically decide which Store
to use, so that the generated table libraries can still be imported by non-root systems.
library Counter {
// Dynamically determine which store to write to based on the context
function set(uint32 value) internal;
// Always write to own storage
function _set(uint32 value) internal;
// ... equivalent functions for all other Store methods
}
#1472 c049c23f
Thanks @alvrs! - - The World
contract now has an initialize
function, which can be called once by the creator of the World to install the core module.
This change allows the registration of all core tables to happen in the CoreModule
, so no table metadata has to be included in the World
's bytecode.
interface IBaseWorld {
function initialize(IModule coreModule) public;
}
The World
contract now stores the original creator of the World
in an immutable state variable.
It is used internally to only allow the original creator to initialize the World
in a separate transaction.
interface IBaseWorld {
function creator() external view returns (address);
}
The deploy script is updated to use the World
's initialize
function to install the CoreModule
instead of registerRootModule
as before.
#1591 251170e1
Thanks @alvrs! - All optional modules have been moved from @latticexyz/world
to @latticexyz/world-modules
.
If you're using the MUD CLI, the import is already updated and no changes are necessary.
#1598 c4f49240
Thanks @dk1a! - Table libraries now correctly handle uninitialized fixed length arrays.
#1581 cea754dd
Thanks @alvrs! - - The external setRecord
and deleteRecord
methods of IStore
no longer accept a FieldLayout
as input, but load it from storage instead.
This is to prevent invalid FieldLayout
values being passed, which could cause the onchain state to diverge from the indexer state.
However, the internal StoreCore
library still exposes a setRecord
and deleteRecord
method that allows a FieldLayout
to be passed.
This is because StoreCore
can only be used internally, so the FieldLayout
value can be trusted and we can save the gas for accessing storage.
interface IStore {
function setRecord(
ResourceId tableId,
bytes32[] calldata keyTuple,
bytes calldata staticData,
PackedCounter encodedLengths,
bytes calldata dynamicData,
- FieldLayout fieldLayout
) external;
function deleteRecord(
ResourceId tableId,
bytes32[] memory keyTuple,
- FieldLayout fieldLayout
) external;
}
The spliceStaticData
method and Store_SpliceStaticData
event of IStore
and StoreCore
no longer include deleteCount
in their signature.
This is because when splicing static data, the data after start
is always overwritten with data
instead of being shifted, so deleteCount
is always the length of the data to be written.
event Store_SpliceStaticData(
ResourceId indexed tableId,
bytes32[] keyTuple,
uint48 start,
- uint40 deleteCount,
bytes data
);
interface IStore {
function spliceStaticData(
ResourceId tableId,
bytes32[] calldata keyTuple,
uint48 start,
- uint40 deleteCount,
bytes calldata data
) external;
}
The updateInField
method has been removed from IStore
, as it's almost identical to the more general spliceDynamicData
.
If you're manually calling updateInField
, here is how to upgrade to spliceDynamicData
:
- store.updateInField(tableId, keyTuple, fieldIndex, startByteIndex, dataToSet, fieldLayout);
+ uint8 dynamicFieldIndex = fieldIndex - fieldLayout.numStaticFields();
+ store.spliceDynamicData(tableId, keyTuple, dynamicFieldIndex, uint40(startByteIndex), uint40(dataToSet.length), dataToSet);
All other methods that are only valid for dynamic fields (pushToField
, popFromField
, getFieldSlice
)
have been renamed to make this more explicit (pushToDynamicField
, popFromDynamicField
, getDynamicFieldSlice
).
Their fieldIndex
parameter has been replaced by a dynamicFieldIndex
parameter, which is the index relative to the first dynamic field (i.e. dynamicFieldIndex
= fieldIndex
- numStaticFields
).
The FieldLayout
parameter has been removed, as it was only used to calculate the dynamicFieldIndex
in the method.
interface IStore {
- function pushToField(
+ function pushToDynamicField(
ResourceId tableId,
bytes32[] calldata keyTuple,
- uint8 fieldIndex,
+ uint8 dynamicFieldIndex,
bytes calldata dataToPush,
- FieldLayout fieldLayout
) external;
- function popFromField(
+ function popFromDynamicField(
ResourceId tableId,
bytes32[] calldata keyTuple,
- uint8 fieldIndex,
+ uint8 dynamicFieldIndex,
uint256 byteLengthToPop,
- FieldLayout fieldLayout
) external;
- function getFieldSlice(
+ function getDynamicFieldSlice(
ResourceId tableId,
bytes32[] memory keyTuple,
- uint8 fieldIndex,
+ uint8 dynamicFieldIndex,
- FieldLayout fieldLayout,
uint256 start,
uint256 end
) external view returns (bytes memory data);
}
IStore
has a new getDynamicFieldLength
length method, which returns the byte length of the given dynamic field and doesn't require the FieldLayout
.
IStore {
+ function getDynamicFieldLength(
+ ResourceId tableId,
+ bytes32[] memory keyTuple,
+ uint8 dynamicFieldIndex
+ ) external view returns (uint256);
}
IStore
now has additional overloads for getRecord
, getField
, getFieldLength
and setField
that don't require a FieldLength
to be passed, but instead load it from storage.
IStore
now exposes setStaticField
and setDynamicField
to save gas by avoiding the dynamic inference of whether the field is static or dynamic.
The getDynamicFieldSlice
method no longer accepts reading outside the bounds of the dynamic field.
This is to avoid returning invalid data, as the data of a dynamic field is not deleted when the record is deleted, but only its length is set to zero.
Updated dependencies [77dce993
, 748f4588
, aea67c58
, 07dd6f32
, c07fa021
, 90e4161b
, 65c9546c
, 331dbfdc
, f9f9609e
, 331dbfdc
, 759514d8
, d5094a24
, 0b8ce3f2
, de151fec
, ae340b2b
, e5d208e4
, 211be2a1
, 0f3e2e02
, 1f80a0b5
, d0878928
, 4c7fd3eb
, a0341daf
, 83583a50
, 5e723b90
, 6573e38e
, 44a5432a
, 6e66c5b7
, 65c9546c
, f8a01a04
, 44a5432a
, 672d05ca
, f1cd43bf
, 31ffc9d5
, 5e723b90
, 63831a26
, 331dbfdc
, 92de5998
, 5741d53d
, 22ee4470
, be313068
, ac508bf1
, 9ff4dd95
, bfcb293d
, 1890f1a0
, 9b43029c
, 55ab88a6
, af639a26
, 5e723b90
, 99ab9cd6
, c049c23f
, 80dd6992
, 24a6cd53
, 708b49c5
, 22ba7b67
, c049c23f
, 251170e1
, c4f49240
, cea754dd
, 95c59b20
]:
Published by github-actions[bot] about 1 year ago
#1484 6573e38e
Thanks @alvrs! - Renamed all occurrences of table
where it is used as "table ID" to tableId
.
This is only a breaking change for consumers who manually decode Store
events, but not for consumers who use the MUD libraries.
event StoreSetRecord(
- bytes32 table,
+ bytes32 tableId,
bytes32[] key,
bytes data
);
event StoreSetField(
- bytes32 table,
+ bytes32 tableId,
bytes32[] key,
uint8 fieldIndex,
bytes data
);
event StoreDeleteRecord(
- bytes32 table,
+ bytes32 tableId,
bytes32[] key
);
event StoreEphemeralRecord(
- bytes32 table,
+ bytes32 tableId,
bytes32[] key,
bytes data
);
#1492 6e66c5b7
Thanks @alvrs! - Renamed all occurrences of key
where it is used as "key tuple" to keyTuple
.
This is only a breaking change for consumers who manually decode Store
events, but not for consumers who use the MUD libraries.
event StoreSetRecord(
bytes32 tableId,
- bytes32[] key,
+ bytes32[] keyTuple,
bytes data
);
event StoreSetField(
bytes32 tableId,
- bytes32[] key,
+ bytes32[] keyTuple,
uint8 fieldIndex,
bytes data
);
event StoreDeleteRecord(
bytes32 tableId,
- bytes32[] key,
+ bytes32[] keyTuple,
);
event StoreEphemeralRecord(
bytes32 tableId,
- bytes32[] key,
+ bytes32[] keyTuple,
bytes data
);
#1558 bfcb293d
Thanks @alvrs! - What used to be known as ephemeral
table is now called offchain
table.
The previous ephemeral
tables only supported an emitEphemeral
method, which emitted a StoreSetEphemeralRecord
event.
Now offchain
tables support all regular table methods, except partial operations on dynamic fields (push
, pop
, update
).
Unlike regular tables they don't store data on-chain but emit the same events as regular tables (StoreSetRecord
, StoreSpliceStaticData
, StoreDeleteRecord
), so their data can be indexed by offchain indexers/clients.
- EphemeralTable.emitEphemeral(value);
+ OffchainTable.set(value);
#1577 af639a26
Thanks @alvrs! - Store
events have been renamed for consistency and readability.
If you're parsing Store
events manually, you need to update your ABI.
If you're using the MUD sync stack, the new events are already integrated and no further changes are necessary.
- event StoreSetRecord(
+ event Store_SetRecord(
ResourceId indexed tableId,
bytes32[] keyTuple,
bytes staticData,
bytes32 encodedLengths,
bytes dynamicData
);
- event StoreSpliceStaticData(
+ event Store_SpliceStaticData(
ResourceId indexed tableId,
bytes32[] keyTuple,
uint48 start,
uint40 deleteCount,
bytes data
);
- event StoreSpliceDynamicData(
+ event Store_SpliceDynamicData(
ResourceId indexed tableId,
bytes32[] keyTuple,
uint48 start,
uint40 deleteCount,
bytes data,
bytes32 encodedLengths
);
- event StoreDeleteRecord(
+ event Store_DeleteRecord(
ResourceId indexed tableId,
bytes32[] keyTuple
);
#1581 cea754dd
Thanks @alvrs! - - The external setRecord
and deleteRecord
methods of IStore
no longer accept a FieldLayout
as input, but load it from storage instead.
This is to prevent invalid FieldLayout
values being passed, which could cause the onchain state to diverge from the indexer state.
However, the internal StoreCore
library still exposes a setRecord
and deleteRecord
method that allows a FieldLayout
to be passed.
This is because StoreCore
can only be used internally, so the FieldLayout
value can be trusted and we can save the gas for accessing storage.
interface IStore {
function setRecord(
ResourceId tableId,
bytes32[] calldata keyTuple,
bytes calldata staticData,
PackedCounter encodedLengths,
bytes calldata dynamicData,
- FieldLayout fieldLayout
) external;
function deleteRecord(
ResourceId tableId,
bytes32[] memory keyTuple,
- FieldLayout fieldLayout
) external;
}
The spliceStaticData
method and Store_SpliceStaticData
event of IStore
and StoreCore
no longer include deleteCount
in their signature.
This is because when splicing static data, the data after start
is always overwritten with data
instead of being shifted, so deleteCount
is always the length of the data to be written.
event Store_SpliceStaticData(
ResourceId indexed tableId,
bytes32[] keyTuple,
uint48 start,
- uint40 deleteCount,
bytes data
);
interface IStore {
function spliceStaticData(
ResourceId tableId,
bytes32[] calldata keyTuple,
uint48 start,
- uint40 deleteCount,
bytes calldata data
) external;
}
The updateInField
method has been removed from IStore
, as it's almost identical to the more general spliceDynamicData
.
If you're manually calling updateInField
, here is how to upgrade to spliceDynamicData
:
- store.updateInField(tableId, keyTuple, fieldIndex, startByteIndex, dataToSet, fieldLayout);
+ uint8 dynamicFieldIndex = fieldIndex - fieldLayout.numStaticFields();
+ store.spliceDynamicData(tableId, keyTuple, dynamicFieldIndex, uint40(startByteIndex), uint40(dataToSet.length), dataToSet);
All other methods that are only valid for dynamic fields (pushToField
, popFromField
, getFieldSlice
)
have been renamed to make this more explicit (pushToDynamicField
, popFromDynamicField
, getDynamicFieldSlice
).
Their fieldIndex
parameter has been replaced by a dynamicFieldIndex
parameter, which is the index relative to the first dynamic field (i.e. dynamicFieldIndex
= fieldIndex
- numStaticFields
).
The FieldLayout
parameter has been removed, as it was only used to calculate the dynamicFieldIndex
in the method.
interface IStore {
- function pushToField(
+ function pushToDynamicField(
ResourceId tableId,
bytes32[] calldata keyTuple,
- uint8 fieldIndex,
+ uint8 dynamicFieldIndex,
bytes calldata dataToPush,
- FieldLayout fieldLayout
) external;
- function popFromField(
+ function popFromDynamicField(
ResourceId tableId,
bytes32[] calldata keyTuple,
- uint8 fieldIndex,
+ uint8 dynamicFieldIndex,
uint256 byteLengthToPop,
- FieldLayout fieldLayout
) external;
- function getFieldSlice(
+ function getDynamicFieldSlice(
ResourceId tableId,
bytes32[] memory keyTuple,
- uint8 fieldIndex,
+ uint8 dynamicFieldIndex,
- FieldLayout fieldLayout,
uint256 start,
uint256 end
) external view returns (bytes memory data);
}
IStore
has a new getDynamicFieldLength
length method, which returns the byte length of the given dynamic field and doesn't require the FieldLayout
.
IStore {
+ function getDynamicFieldLength(
+ ResourceId tableId,
+ bytes32[] memory keyTuple,
+ uint8 dynamicFieldIndex
+ ) external view returns (uint256);
}
IStore
now has additional overloads for getRecord
, getField
, getFieldLength
and setField
that don't require a FieldLength
to be passed, but instead load it from storage.
IStore
now exposes setStaticField
and setDynamicField
to save gas by avoiding the dynamic inference of whether the field is static or dynamic.
The getDynamicFieldSlice
method no longer accepts reading outside the bounds of the dynamic field.
This is to avoid returning invalid data, as the data of a dynamic field is not deleted when the record is deleted, but only its length is set to zero.
Updated dependencies [65c9546c
, 331dbfdc
, 0b8ce3f2
, 44a5432a
, 331dbfdc
, 92de5998
, bfcb293d
, 5e723b90
, 24a6cd53
, 708b49c5
, c4f49240
, cea754dd
]:
Published by github-actions[bot] about 1 year ago
Published by github-actions[bot] about 1 year ago
#1458 b9e562d8
Thanks @alvrs! - The World
now performs ERC165
interface checks to ensure that the StoreHook
, SystemHook
, System
, DelegationControl
and Module
contracts to actually implement their respective interfaces before registering them in the World.
The required supportsInterface
methods are implemented on the respective base contracts.
When creating one of these contracts, the recommended approach is to extend the base contract rather than the interface.
- import { IStoreHook } from "@latticexyz/store/src/IStore.sol";
+ import { StoreHook } from "@latticexyz/store/src/StoreHook.sol";
- contract MyStoreHook is IStoreHook {}
+ contract MyStoreHook is StoreHook {}
- import { ISystemHook } from "@latticexyz/world/src/interfaces/ISystemHook.sol";
+ import { SystemHook } from "@latticexyz/world/src/SystemHook.sol";
- contract MySystemHook is ISystemHook {}
+ contract MySystemHook is SystemHook {}
- import { IDelegationControl } from "@latticexyz/world/src/interfaces/IDelegationControl.sol";
+ import { DelegationControl } from "@latticexyz/world/src/DelegationControl.sol";
- contract MyDelegationControl is IDelegationControl {}
+ contract MyDelegationControl is DelegationControl {}
- import { IModule } from "@latticexyz/world/src/interfaces/IModule.sol";
+ import { Module } from "@latticexyz/world/src/Module.sol";
- contract MyModule is IModule {}
+ contract MyModule is Module {}
#1457 51914d65
Thanks @alvrs! - - The access control library no longer allows calls by the World
contract to itself to bypass the ownership check.
This is a breaking change for root modules that relied on this mechanism to register root tables, systems or function selectors.
To upgrade, root modules must use delegatecall
instead of a regular call
to install root tables, systems or function selectors.
- world.registerSystem(rootSystemId, rootSystemAddress);
+ address(world).delegatecall(abi.encodeCall(world.registerSystem, (rootSystemId, rootSystemAddress)));
installRoot
method was added to the IModule
interface.world.installRootModule
.world.installModule
, the module's install
function continues to be called.#1425 2ca75f9b
Thanks @alvrs! - The World now maintains a balance per namespace.
When a system is called with value, the value stored in the World contract and credited to the system's namespace.
Previously, the World contract did not store value, but passed it on to the system contracts.
However, as systems are expected to be stateless (reading/writing state only via the calling World) and can be registered in multiple Worlds, this could have led to exploits.
Any address with access to a namespace can use the balance of that namespace.
This allows all systems registered in the same namespace to work with the same balance.
There are two new World methods to transfer balance between namespaces (transferBalanceToNamespace
) or to an address (transferBalanceToAddress
).
interface IBaseWorld {
function transferBalanceToNamespace(bytes16 fromNamespace, bytes16 toNamespace, uint256 amount) external;
function transferBalanceToAddress(bytes16 fromNamespace, address toAddress, uint256 amount) external;
}
#1422 1d60930d
Thanks @alvrs! - It is now possible to unregister Store hooks and System hooks.
interface IStore {
function unregisterStoreHook(bytes32 table, IStoreHook hookAddress) external;
// ...
}
interface IWorld {
function unregisterSystemHook(bytes32 resourceSelector, ISystemHook hookAddress) external;
// ...
}
1d60930d
, b9e562d8
, 5e71e1cb
]:
Published by github-actions[bot] about 1 year ago
Published by github-actions[bot] about 1 year ago
1d60930d
, b9e562d8
, 51914d65
, 2ca75f9b
, 5e71e1cb
, 5e71e1cb
]:
Published by github-actions[bot] about 1 year ago
1d60930d
, b9e562d8
, 5e71e1cb
]:
Published by github-actions[bot] about 1 year ago
#1458 b9e562d8
Thanks @alvrs! - The World
now performs ERC165
interface checks to ensure that the StoreHook
, SystemHook
, System
, DelegationControl
and Module
contracts to actually implement their respective interfaces before registering them in the World.
The required supportsInterface
methods are implemented on the respective base contracts.
When creating one of these contracts, the recommended approach is to extend the base contract rather than the interface.
- import { IStoreHook } from "@latticexyz/store/src/IStore.sol";
+ import { StoreHook } from "@latticexyz/store/src/StoreHook.sol";
- contract MyStoreHook is IStoreHook {}
+ contract MyStoreHook is StoreHook {}
- import { ISystemHook } from "@latticexyz/world/src/interfaces/ISystemHook.sol";
+ import { SystemHook } from "@latticexyz/world/src/SystemHook.sol";
- contract MySystemHook is ISystemHook {}
+ contract MySystemHook is SystemHook {}
- import { IDelegationControl } from "@latticexyz/world/src/interfaces/IDelegationControl.sol";
+ import { DelegationControl } from "@latticexyz/world/src/DelegationControl.sol";
- contract MyDelegationControl is IDelegationControl {}
+ contract MyDelegationControl is DelegationControl {}
- import { IModule } from "@latticexyz/world/src/interfaces/IModule.sol";
+ import { Module } from "@latticexyz/world/src/Module.sol";
- contract MyModule is IModule {}
+ contract MyModule is Module {}
#1422 1d60930d
Thanks @alvrs! - It is now possible to unregister Store hooks and System hooks.
interface IStore {
function unregisterStoreHook(bytes32 table, IStoreHook hookAddress) external;
// ...
}
interface IWorld {
function unregisterSystemHook(bytes32 resourceSelector, ISystemHook hookAddress) external;
// ...
}
#1443 5e71e1cb
Thanks @holic! - Moved KeySchema
, ValueSchema
, SchemaToPrimitives
and TableRecord
types into @latticexyz/protocol-parser
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
Published by github-actions[bot] about 1 year ago
1d60930d
, b9e562d8
, 5e71e1cb
]:
Published by github-actions[bot] about 1 year ago
5e71e1cb
Thanks @holic! - Adds decodeKey
, decodeValue
, encodeKey
, and encodeValue
helpers to decode/encode from key/value schemas. Deprecates previous methods that use a schema object with static/dynamic field arrays, originally attempting to model our on-chain behavior but ended up not very ergonomic when working with table configs.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