Skip to content

Commit

Permalink
fix(store): enforce unique table names across types (#2736)
Browse files Browse the repository at this point in the history
Co-authored-by: Kevin Ingersoll <kingersoll@gmail.com>
  • Loading branch information
yonadaaa and holic committed Apr 25, 2024
1 parent 91e5b89 commit ed404b7
Show file tree
Hide file tree
Showing 15 changed files with 238 additions and 39 deletions.
5 changes: 5 additions & 0 deletions .changeset/strong-lobsters-shake.md
@@ -0,0 +1,5 @@
---
"@latticexyz/store": patch
---

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
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
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
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
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
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
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)));
}

Expand Down
2 changes: 1 addition & 1 deletion packages/store/src/version.sol
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
@@ -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
@@ -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
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

0 comments on commit ed404b7

Please sign in to comment.