Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(store): enforce unique table names across types #2736

Merged
merged 14 commits into from
Apr 25, 2024
5 changes: 5 additions & 0 deletions .changeset/strong-lobsters-shake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@latticexyz/store": patch
holic marked this conversation as resolved.
Show resolved Hide resolved
---

Added a check to `registerTable` that prevents registering both an offchain and onchain table with the same name, making it easier to use human-readable names in indexers.
22 changes: 21 additions & 1 deletion docs/pages/store/reference/misc.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1850,6 +1850,26 @@ function getType(ResourceId resourceId) internal pure returns (bytes2);
| -------- | -------- | ------------------------------------- |
| `<none>` | `bytes2` | The extracted 2-byte type identifier. |

#### getResourceName

Get the name from a resource ID.

```solidity
function getResourceName(ResourceId resourceId) internal pure returns (bytes30);
```

**Parameters**

| Name | Type | Description |
| ------------ | ------------ | ---------------- |
| `resourceId` | `ResourceId` | The resource ID. |

**Returns**

| Name | Type | Description |
| -------- | --------- | --------------- |
| `<none>` | `bytes30` | A 30-byte name. |

## ResourceIdLib

[Git Source](https://github.com/latticexyz/mud/blob/main/packages/store/src/ResourceId.sol)
Expand Down Expand Up @@ -2688,5 +2708,5 @@ Contains a constant representing the version of the Store protocol.
_Identifier for the current Store protocol version._

```solidity
bytes32 constant STORE_VERSION = "2.0.1";
bytes32 constant STORE_VERSION = "2.0.2";
```
2 changes: 1 addition & 1 deletion docs/pages/world/reference/misc.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -104,5 +104,5 @@ Contains a constant representing the version of the World protocol.
_Identifier for the current World protocol version._

```solidity
bytes32 constant WORLD_VERSION = "2.0.1";
bytes32 constant WORLD_VERSION = "2.0.2";
```
4 changes: 2 additions & 2 deletions packages/cli/src/deploy/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ export const worldDeployEvents = [helloStoreEvent, helloWorldEvent] as const;
export const worldAbi = [...IBaseWorldAbi, ...IModuleAbi] as const;

// Ideally, this should be an append-only list. Before adding more versions here, be sure to add backwards-compatible support for old Store/World versions.
export const supportedStoreVersions = ["2.0.0", "2.0.1"];
export const supportedWorldVersions = ["2.0.0", "2.0.1"];
export const supportedStoreVersions = ["2.0.0", "2.0.1", "2.0.2"];
export const supportedWorldVersions = ["2.0.0", "2.0.1", "2.0.2"];

// TODO: extend this to include factory+deployer address? so we can reuse the deployer for a world?
export type WorldDeploy = {
Expand Down
26 changes: 13 additions & 13 deletions packages/store/gas-report.json
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@
"file": "test/KeyEncoding.t.sol",
"test": "testRegisterAndGetFieldLayout",
"name": "register KeyEncoding table",
"gasUsed": 717787
"gasUsed": 721008
},
{
"file": "test/Mixed.t.sol",
Expand Down Expand Up @@ -669,7 +669,7 @@
"file": "test/StoreCoreGas.t.sol",
"test": "testAccessEmptyData",
"name": "access length of dynamic field of non-existing record",
"gasUsed": 1158
"gasUsed": 1159
},
{
"file": "test/StoreCoreGas.t.sol",
Expand Down Expand Up @@ -771,13 +771,13 @@
"file": "test/StoreCoreGas.t.sol",
"test": "testRegisterAndGetFieldLayout",
"name": "StoreCore: register table",
"gasUsed": 647841
"gasUsed": 651056
},
{
"file": "test/StoreCoreGas.t.sol",
"test": "testRegisterAndGetFieldLayout",
"name": "StoreCore: get field layout (warm)",
"gasUsed": 508
"gasUsed": 509
},
{
"file": "test/StoreCoreGas.t.sol",
Expand All @@ -795,13 +795,13 @@
"file": "test/StoreCoreGas.t.sol",
"test": "testSetAndGetDynamicData",
"name": "set complex record with dynamic data (4 slots)",
"gasUsed": 101866
"gasUsed": 101867
},
{
"file": "test/StoreCoreGas.t.sol",
"test": "testSetAndGetDynamicData",
"name": "get complex record with dynamic data (4 slots)",
"gasUsed": 4235
"gasUsed": 4234
},
{
"file": "test/StoreCoreGas.t.sol",
Expand All @@ -813,7 +813,7 @@
"file": "test/StoreCoreGas.t.sol",
"test": "testSetAndGetDynamicData",
"name": "compare: Set complex record with dynamic data using abi.encode",
"gasUsed": 267534
"gasUsed": 267535
},
{
"file": "test/StoreCoreGas.t.sol",
Expand All @@ -825,13 +825,13 @@
"file": "test/StoreCoreGas.t.sol",
"test": "testSetAndGetDynamicDataLength",
"name": "set dynamic length of dynamic index 1",
"gasUsed": 967
"gasUsed": 968
},
{
"file": "test/StoreCoreGas.t.sol",
"test": "testSetAndGetDynamicDataLength",
"name": "reduce dynamic length of dynamic index 0",
"gasUsed": 957
"gasUsed": 958
},
{
"file": "test/StoreCoreGas.t.sol",
Expand Down Expand Up @@ -873,7 +873,7 @@
"file": "test/StoreCoreGas.t.sol",
"test": "testSetAndGetField",
"name": "set dynamic field (1 slot, second dynamic field)",
"gasUsed": 32246
"gasUsed": 32247
},
{
"file": "test/StoreCoreGas.t.sol",
Expand All @@ -891,13 +891,13 @@
"file": "test/StoreCoreGas.t.sol",
"test": "testSetAndGetStaticData",
"name": "get static record (1 slot)",
"gasUsed": 1551
"gasUsed": 1552
},
{
"file": "test/StoreCoreGas.t.sol",
"test": "testSetAndGetStaticDataSpanningWords",
"name": "set static record (2 slots)",
"gasUsed": 54649
"gasUsed": 54650
},
{
"file": "test/StoreCoreGas.t.sol",
Expand Down Expand Up @@ -1113,7 +1113,7 @@
"file": "test/Vector2.t.sol",
"test": "testRegisterAndGetFieldLayout",
"name": "register Vector2 field layout",
"gasUsed": 443849
"gasUsed": 447063
},
{
"file": "test/Vector2.t.sol",
Expand Down
9 changes: 9 additions & 0 deletions packages/store/src/ResourceId.sol
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,13 @@ library ResourceIdInstance {
function getType(ResourceId resourceId) internal pure returns (bytes2) {
return bytes2(ResourceId.unwrap(resourceId));
}

/**
* @notice Get the name from a resource ID.
* @param resourceId The resource ID.
* @return A 30-byte name.
*/
function getResourceName(ResourceId resourceId) internal pure returns (bytes30) {
return bytes30(ResourceId.unwrap(resourceId) << (TYPE_BITS));
}
}
10 changes: 6 additions & 4 deletions packages/store/src/StoreCore.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { IStoreHook } from "./IStoreHook.sol";
import { StoreSwitch } from "./StoreSwitch.sol";
import { Hook, HookLib } from "./Hook.sol";
import { BEFORE_SET_RECORD, AFTER_SET_RECORD, BEFORE_SPLICE_STATIC_DATA, AFTER_SPLICE_STATIC_DATA, BEFORE_SPLICE_DYNAMIC_DATA, AFTER_SPLICE_DYNAMIC_DATA, BEFORE_DELETE_RECORD, AFTER_DELETE_RECORD } from "./storeHookTypes.sol";
import { ResourceId } from "./ResourceId.sol";
import { ResourceId, ResourceIdLib } from "./ResourceId.sol";
import { RESOURCE_TABLE, RESOURCE_OFFCHAIN_TABLE } from "./storeResourceTypes.sol";
import { IStoreEvents } from "./IStoreEvents.sol";

Expand Down Expand Up @@ -160,7 +160,7 @@ library StoreCore {
string[] memory keyNames,
string[] memory fieldNames
) internal {
// Verify the table ID is of type RESOURCE_TABLE
// Verify the table ID is of type RESOURCE_TABLE or RESOURCE_OFFCHAIN_TABLE
if (tableId.getType() != RESOURCE_TABLE && tableId.getType() != RESOURCE_OFFCHAIN_TABLE) {
revert IStoreErrors.Store_InvalidResourceType(RESOURCE_TABLE, tableId, string(abi.encodePacked(tableId)));
}
Expand Down Expand Up @@ -209,8 +209,10 @@ library StoreCore {
}
}

// Verify there is no resource with this ID yet
if (ResourceIds._getExists(tableId)) {
// Verify that there is no table or offchain table with the same name
ResourceId onchainTableId = ResourceIdLib.encode(RESOURCE_TABLE, tableId.getResourceName());
ResourceId offchainTableId = ResourceIdLib.encode(RESOURCE_OFFCHAIN_TABLE, tableId.getResourceName());
if (ResourceIds._getExists(onchainTableId) || ResourceIds._getExists(offchainTableId)) {
revert IStoreErrors.Store_TableAlreadyExists(tableId, string(abi.encodePacked(tableId)));
}
holic marked this conversation as resolved.
Show resolved Hide resolved

Expand Down
2 changes: 1 addition & 1 deletion packages/store/src/version.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ pragma solidity >=0.8.24;
*/

/// @dev Identifier for the current Store protocol version.
bytes32 constant STORE_VERSION = "2.0.1";
bytes32 constant STORE_VERSION = "2.0.2";
57 changes: 57 additions & 0 deletions packages/store/ts/protocol-snapshots/2.0.2.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
[
"error EncodedLengths_InvalidLength(uint256 length)",
"error FieldLayout_Empty()",
"error FieldLayout_InvalidStaticDataLength(uint256 staticDataLength, uint256 computedStaticDataLength)",
"error FieldLayout_StaticLengthDoesNotFitInAWord(uint256 index)",
"error FieldLayout_StaticLengthIsNotZero(uint256 index)",
"error FieldLayout_StaticLengthIsZero(uint256 index)",
"error FieldLayout_TooManyDynamicFields(uint256 numFields, uint256 maxFields)",
"error FieldLayout_TooManyFields(uint256 numFields, uint256 maxFields)",
"error Schema_InvalidLength(uint256 length)",
"error Schema_StaticTypeAfterDynamicType()",
"error Slice_OutOfBounds(bytes data, uint256 start, uint256 end)",
"error Store_IndexOutOfBounds(uint256 length, uint256 accessedIndex)",
"error Store_InvalidBounds(uint256 start, uint256 end)",
"error Store_InvalidFieldNamesLength(uint256 expected, uint256 received)",
"error Store_InvalidKeyNamesLength(uint256 expected, uint256 received)",
"error Store_InvalidResourceType(bytes2 expected, bytes32 resourceId, string resourceIdString)",
"error Store_InvalidSplice(uint40 startWithinField, uint40 deleteCount, uint40 fieldLength)",
"error Store_InvalidStaticDataLength(uint256 expected, uint256 received)",
"error Store_InvalidValueSchemaDynamicLength(uint256 expected, uint256 received)",
"error Store_InvalidValueSchemaLength(uint256 expected, uint256 received)",
"error Store_InvalidValueSchemaStaticLength(uint256 expected, uint256 received)",
"error Store_TableAlreadyExists(bytes32 tableId, string tableIdString)",
"error Store_TableNotFound(bytes32 tableId, string tableIdString)",
"event HelloStore(bytes32 indexed storeVersion)",
"event Store_DeleteRecord(bytes32 indexed tableId, bytes32[] keyTuple)",
"event Store_SetRecord(bytes32 indexed tableId, bytes32[] keyTuple, bytes staticData, bytes32 encodedLengths, bytes dynamicData)",
"event Store_SpliceDynamicData(bytes32 indexed tableId, bytes32[] keyTuple, uint8 dynamicFieldIndex, uint48 start, uint40 deleteCount, bytes32 encodedLengths, bytes data)",
"event Store_SpliceStaticData(bytes32 indexed tableId, bytes32[] keyTuple, uint48 start, bytes data)",
"function deleteRecord(bytes32 tableId, bytes32[] keyTuple)",
"function getDynamicField(bytes32 tableId, bytes32[] keyTuple, uint8 dynamicFieldIndex) view returns (bytes)",
"function getDynamicFieldLength(bytes32 tableId, bytes32[] keyTuple, uint8 dynamicFieldIndex) view returns (uint256)",
"function getDynamicFieldSlice(bytes32 tableId, bytes32[] keyTuple, uint8 dynamicFieldIndex, uint256 start, uint256 end) view returns (bytes data)",
"function getField(bytes32 tableId, bytes32[] keyTuple, uint8 fieldIndex, bytes32 fieldLayout) view returns (bytes data)",
"function getField(bytes32 tableId, bytes32[] keyTuple, uint8 fieldIndex) view returns (bytes data)",
"function getFieldLayout(bytes32 tableId) view returns (bytes32 fieldLayout)",
"function getFieldLength(bytes32 tableId, bytes32[] keyTuple, uint8 fieldIndex, bytes32 fieldLayout) view returns (uint256)",
"function getFieldLength(bytes32 tableId, bytes32[] keyTuple, uint8 fieldIndex) view returns (uint256)",
"function getKeySchema(bytes32 tableId) view returns (bytes32 keySchema)",
"function getRecord(bytes32 tableId, bytes32[] keyTuple, bytes32 fieldLayout) view returns (bytes staticData, bytes32 encodedLengths, bytes dynamicData)",
"function getRecord(bytes32 tableId, bytes32[] keyTuple) view returns (bytes staticData, bytes32 encodedLengths, bytes dynamicData)",
"function getStaticField(bytes32 tableId, bytes32[] keyTuple, uint8 fieldIndex, bytes32 fieldLayout) view returns (bytes32)",
"function getValueSchema(bytes32 tableId) view returns (bytes32 valueSchema)",
"function popFromDynamicField(bytes32 tableId, bytes32[] keyTuple, uint8 dynamicFieldIndex, uint256 byteLengthToPop)",
"function pushToDynamicField(bytes32 tableId, bytes32[] keyTuple, uint8 dynamicFieldIndex, bytes dataToPush)",
"function registerStoreHook(bytes32 tableId, address hookAddress, uint8 enabledHooksBitmap)",
"function registerTable(bytes32 tableId, bytes32 fieldLayout, bytes32 keySchema, bytes32 valueSchema, string[] keyNames, string[] fieldNames)",
"function setDynamicField(bytes32 tableId, bytes32[] keyTuple, uint8 dynamicFieldIndex, bytes data)",
"function setField(bytes32 tableId, bytes32[] keyTuple, uint8 fieldIndex, bytes data, bytes32 fieldLayout)",
"function setField(bytes32 tableId, bytes32[] keyTuple, uint8 fieldIndex, bytes data)",
"function setRecord(bytes32 tableId, bytes32[] keyTuple, bytes staticData, bytes32 encodedLengths, bytes dynamicData)",
"function setStaticField(bytes32 tableId, bytes32[] keyTuple, uint8 fieldIndex, bytes data, bytes32 fieldLayout)",
"function spliceDynamicData(bytes32 tableId, bytes32[] keyTuple, uint8 dynamicFieldIndex, uint40 startWithinField, uint40 deleteCount, bytes data)",
"function spliceStaticData(bytes32 tableId, bytes32[] keyTuple, uint48 start, bytes data)",
"function storeVersion() view returns (bytes32 version)",
"function unregisterStoreHook(bytes32 tableId, address hookAddress)",
]
2 changes: 2 additions & 0 deletions packages/store/ts/protocolVersions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// History of protocol versions and a short description of what changed in each.
export const protocolVersions = {
"2.0.2":
"Patched `StoreCore.registerTable` to prevent registering both an offchain and onchain table with the same name.",
"2.0.1": "Patched `StoreRead.getDynamicFieldLength` to use the correct method to read the dynamic field length.",
"2.0.0": "Initial v2 release. See mud.dev/changelog for the full list of changes from v1.",
};
24 changes: 12 additions & 12 deletions packages/world-modules/gas-report.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"file": "test/CallWithSignatureModule.t.sol",
"test": "testInstallRoot",
"name": "install delegation module",
"gasUsed": 687912
"gasUsed": 691133
},
{
"file": "test/CallWithSignatureModule.t.sol",
Expand Down Expand Up @@ -87,13 +87,13 @@
"file": "test/KeysInTableModule.t.sol",
"test": "testInstallComposite",
"name": "install keys in table module",
"gasUsed": 1456815
"gasUsed": 1463317
},
{
"file": "test/KeysInTableModule.t.sol",
"test": "testInstallGas",
"name": "install keys in table module",
"gasUsed": 1456815
"gasUsed": 1463317
},
{
"file": "test/KeysInTableModule.t.sol",
Expand All @@ -105,13 +105,13 @@
"file": "test/KeysInTableModule.t.sol",
"test": "testInstallSingleton",
"name": "install keys in table module",
"gasUsed": 1456815
"gasUsed": 1463317
},
{
"file": "test/KeysInTableModule.t.sol",
"test": "testSetAndDeleteRecordHookCompositeGas",
"name": "install keys in table module",
"gasUsed": 1456815
"gasUsed": 1463317
},
{
"file": "test/KeysInTableModule.t.sol",
Expand All @@ -129,7 +129,7 @@
"file": "test/KeysInTableModule.t.sol",
"test": "testSetAndDeleteRecordHookGas",
"name": "install keys in table module",
"gasUsed": 1456815
"gasUsed": 1463317
},
{
"file": "test/KeysInTableModule.t.sol",
Expand All @@ -147,7 +147,7 @@
"file": "test/KeysWithValueModule.t.sol",
"test": "testGetKeysWithValueGas",
"name": "install keys with value module",
"gasUsed": 715762
"gasUsed": 719012
},
{
"file": "test/KeysWithValueModule.t.sol",
Expand All @@ -165,7 +165,7 @@
"file": "test/KeysWithValueModule.t.sol",
"test": "testInstall",
"name": "install keys with value module",
"gasUsed": 715762
"gasUsed": 719012
},
{
"file": "test/KeysWithValueModule.t.sol",
Expand All @@ -177,7 +177,7 @@
"file": "test/KeysWithValueModule.t.sol",
"test": "testSetAndDeleteRecordHook",
"name": "install keys with value module",
"gasUsed": 715762
"gasUsed": 719012
},
{
"file": "test/KeysWithValueModule.t.sol",
Expand All @@ -195,7 +195,7 @@
"file": "test/KeysWithValueModule.t.sol",
"test": "testSetField",
"name": "install keys with value module",
"gasUsed": 715762
"gasUsed": 719012
},
{
"file": "test/KeysWithValueModule.t.sol",
Expand Down Expand Up @@ -315,7 +315,7 @@
"file": "test/UniqueEntityModule.t.sol",
"test": "testInstall",
"name": "install unique entity module",
"gasUsed": 715495
"gasUsed": 718745
},
{
"file": "test/UniqueEntityModule.t.sol",
Expand All @@ -327,7 +327,7 @@
"file": "test/UniqueEntityModule.t.sol",
"test": "testInstallRoot",
"name": "installRoot unique entity module",
"gasUsed": 685964
"gasUsed": 689190
},
{
"file": "test/UniqueEntityModule.t.sol",
Expand Down
Loading
Loading