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

feat(world): prevent invalid namespace strings [M-05] #2169

Merged
merged 11 commits into from
Jan 22, 2024
9 changes: 9 additions & 0 deletions .changeset/tidy-stingrays-think.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@latticexyz/world": major
---

Namespaces are not allowed to contain double underscores ("\_\_") anymore, as this sequence of characters is used to [separate the namespace and function selector](https://github.com/latticexyz/mud/pull/2168) in namespaced systems.
This is to prevent signature clashes of functions in different namespaces.

(Example: If namespaces were allowed to contain this separator string, a function "function" in namespace "namespace**my" would result in the namespaced function selector "namespace**my**function",
alvrs marked this conversation as resolved.
Show resolved Hide resolved
and would clash with a function "my**function" in namespace "namespace".)
alvrs marked this conversation as resolved.
Show resolved Hide resolved
12 changes: 6 additions & 6 deletions packages/world-modules/gas-report.json
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@
"file": "test/KeysWithValueModule.t.sol",
"test": "testGetKeysWithValueGas",
"name": "install keys with value module",
"gasUsed": 683899
"gasUsed": 687430
},
{
"file": "test/KeysWithValueModule.t.sol",
Expand All @@ -153,7 +153,7 @@
"file": "test/KeysWithValueModule.t.sol",
"test": "testInstall",
"name": "install keys with value module",
"gasUsed": 683899
"gasUsed": 687430
},
{
"file": "test/KeysWithValueModule.t.sol",
Expand All @@ -165,7 +165,7 @@
"file": "test/KeysWithValueModule.t.sol",
"test": "testSetAndDeleteRecordHook",
"name": "install keys with value module",
"gasUsed": 683899
"gasUsed": 687430
},
{
"file": "test/KeysWithValueModule.t.sol",
Expand All @@ -183,7 +183,7 @@
"file": "test/KeysWithValueModule.t.sol",
"test": "testSetField",
"name": "install keys with value module",
"gasUsed": 683899
"gasUsed": 687430
},
{
"file": "test/KeysWithValueModule.t.sol",
Expand Down Expand Up @@ -303,7 +303,7 @@
"file": "test/UniqueEntityModule.t.sol",
"test": "testInstall",
"name": "install unique entity module",
"gasUsed": 701756
"gasUsed": 705287
},
{
"file": "test/UniqueEntityModule.t.sol",
Expand All @@ -315,7 +315,7 @@
"file": "test/UniqueEntityModule.t.sol",
"test": "testInstallRoot",
"name": "installRoot unique entity module",
"gasUsed": 670623
"gasUsed": 674154
},
{
"file": "test/UniqueEntityModule.t.sol",
Expand Down
10 changes: 5 additions & 5 deletions packages/world/gas-report.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
"file": "test/Factories.t.sol",
"test": "testWorldFactory",
"name": "deploy world via WorldFactory",
"gasUsed": 13041829
"gasUsed": 13045360
},
{
"file": "test/World.t.sol",
Expand Down Expand Up @@ -111,7 +111,7 @@
"file": "test/World.t.sol",
"test": "testRegisterNamespace",
"name": "Register a new namespace",
"gasUsed": 120951
"gasUsed": 124482
},
{
"file": "test/World.t.sol",
Expand All @@ -129,13 +129,13 @@
"file": "test/World.t.sol",
"test": "testRegisterTable",
"name": "Register a new table in the namespace",
"gasUsed": 536881
"gasUsed": 536849
},
{
"file": "test/World.t.sol",
"test": "testRenounceNamespace",
"name": "Renounce namespace ownership",
"gasUsed": 36773
"gasUsed": 40304
},
{
"file": "test/World.t.sol",
Expand All @@ -153,7 +153,7 @@
"file": "test/World.t.sol",
"test": "testUnregisterNamespaceDelegation",
"name": "unregister a namespace delegation",
"gasUsed": 28360
"gasUsed": 31891
},
{
"file": "test/World.t.sol",
Expand Down
6 changes: 6 additions & 0 deletions packages/world/src/IWorldErrors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ interface IWorldErrors {
*/
error World_InvalidResourceId(ResourceId resourceId, string resourceIdString);

/**
* @notice Raised when an namespace contains an invalid sequence of characters ("__").
* @param namespace The invalid namespace.
*/
error World_InvalidNamespace(bytes14 namespace);

/**
* @notice Raised when trying to register a system that already exists.
* @param system The address of the system.
Expand Down
1 change: 1 addition & 0 deletions packages/world/src/WorldResourceId.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { ResourceId, ResourceIdInstance, TYPE_BITS } from "@latticexyz/store/src
import { ROOT_NAMESPACE, ROOT_NAME } from "./constants.sol";
import { RESOURCE_NAMESPACE, MASK_RESOURCE_NAMESPACE } from "./worldResourceTypes.sol";

uint256 constant NAMESPACE_BYTES = 14;
uint256 constant NAMESPACE_BITS = 14 * 8; // 14 bytes * 8 bits per byte
uint256 constant NAME_BITS = 16 * 8; // 16 bytes * 8 bits per byte

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { AccessControl } from "../../../AccessControl.sol";
import { ResourceId } from "../../../WorldResourceId.sol";
import { ResourceAccess } from "../../../codegen/tables/ResourceAccess.sol";
import { NamespaceOwner } from "../../../codegen/tables/NamespaceOwner.sol";
import { requireNamespace } from "../../../requireNamespace.sol";
import { validateNamespace } from "../../../validateNamespace.sol";

import { LimitedCallContext } from "../LimitedCallContext.sol";

Expand Down Expand Up @@ -53,8 +53,8 @@ contract AccessManagementSystem is System, LimitedCallContext {
* @param newOwner The address to which ownership should be transferred.
*/
function transferOwnership(ResourceId namespaceId, address newOwner) public virtual onlyDelegatecall {
// Require the namespace ID to be a valid namespace
requireNamespace(namespaceId);
// Require the namespace to be a valid namespace ID
alvrs marked this conversation as resolved.
Show resolved Hide resolved
validateNamespace(namespaceId);

// Require the namespace to exist
AccessControl.requireExistence(namespaceId);
Expand All @@ -79,7 +79,7 @@ contract AccessManagementSystem is System, LimitedCallContext {
*/
function renounceOwnership(ResourceId namespaceId) public virtual onlyDelegatecall {
// Require the namespace ID to be a valid namespace
requireNamespace(namespaceId);
validateNamespace(namespaceId);

// Require the namespace to exist
AccessControl.requireExistence(namespaceId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { RESOURCE_NAMESPACE } from "../../../worldResourceTypes.sol";
import { IWorldErrors } from "../../../IWorldErrors.sol";

import { Balances } from "../../../codegen/tables/Balances.sol";
import { requireNamespace } from "../../../requireNamespace.sol";
import { validateNamespace } from "../../../validateNamespace.sol";

import { LimitedCallContext } from "../LimitedCallContext.sol";

Expand All @@ -35,9 +35,9 @@ contract BalanceTransferSystem is System, IWorldErrors, LimitedCallContext {
uint256 amount
) public virtual onlyDelegatecall {
// Require the from namespace to be a valid namespace ID
requireNamespace(fromNamespaceId);
validateNamespace(fromNamespaceId);
// Require the to namespace to be a valid namespace ID
requireNamespace(toNamespaceId);
validateNamespace(toNamespaceId);

// Require the namespace to exist
AccessControl.requireExistence(toNamespaceId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { SystemRegistry } from "../../../codegen/tables/SystemRegistry.sol";
import { Systems } from "../../../codegen/tables/Systems.sol";
import { FunctionSelectors } from "../../../codegen/tables/FunctionSelectors.sol";
import { FunctionSignatures } from "../../../codegen/tables/FunctionSignatures.sol";
import { requireNamespace } from "../../../requireNamespace.sol";
import { validateNamespace } from "../../../validateNamespace.sol";

import { LimitedCallContext } from "../LimitedCallContext.sol";

Expand All @@ -44,8 +44,8 @@ contract WorldRegistrationSystem is System, IWorldErrors, LimitedCallContext {
* @param namespaceId The unique identifier for the new namespace
*/
function registerNamespace(ResourceId namespaceId) public virtual onlyDelegatecall {
// Require namespace ID to be a valid namespace
requireNamespace(namespaceId);
// Require namespace to be a valid namespace ID
alvrs marked this conversation as resolved.
Show resolved Hide resolved
validateNamespace(namespaceId);

// Require namespace to not exist yet
if (ResourceIds._getExists(namespaceId)) {
Expand Down Expand Up @@ -299,8 +299,8 @@ contract WorldRegistrationSystem is System, IWorldErrors, LimitedCallContext {
ResourceId delegationControlId,
bytes memory initCallData
) public onlyDelegatecall {
// Require namespace ID to be a valid namespace
requireNamespace(namespaceId);
// Require namespace to be a valid namespace ID
alvrs marked this conversation as resolved.
Show resolved Hide resolved
validateNamespace(namespaceId);

// Require the delegation to not be unlimited
if (!Delegation.isLimited(delegationControlId)) {
Expand Down Expand Up @@ -335,7 +335,7 @@ contract WorldRegistrationSystem is System, IWorldErrors, LimitedCallContext {
*/
function unregisterNamespaceDelegation(ResourceId namespaceId) public onlyDelegatecall {
// Require namespace ID to be a valid namespace
requireNamespace(namespaceId);
validateNamespace(namespaceId);

// Require the namespace to exist
AccessControl.requireExistence(namespaceId);
Expand Down
19 changes: 0 additions & 19 deletions packages/world/src/requireNamespace.sol

This file was deleted.

31 changes: 31 additions & 0 deletions packages/world/src/validateNamespace.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.21;

import { Bytes } from "@latticexyz/store/src/Bytes.sol";

import { ResourceId, WorldResourceIdInstance, NAMESPACE_BYTES } from "./WorldResourceId.sol";
import { RESOURCE_NAMESPACE } from "./worldResourceTypes.sol";
import { IWorldErrors } from "./IWorldErrors.sol";

using WorldResourceIdInstance for ResourceId;

/**
* @notice Checks if a given `resourceId` is a valid namespace.
* @dev Reverts with IWorldErrors.World_InvalidResourceType if the ID does not have the correct components.
* @dev Reverts with IWorldErrors.World_InvalidNamespace if the namespace includes the reserved separator string ("__").
* @param resourceId The resource ID to verify.
*/
function validateNamespace(ResourceId resourceId) pure {
// Require the resourceId to have the namespace type
if (ResourceId.unwrap(resourceId) != ResourceId.unwrap(resourceId.getNamespaceId())) {
revert IWorldErrors.World_InvalidResourceType(RESOURCE_NAMESPACE, resourceId, resourceId.toString());
}

// Require the namespace to not include the reserved separator
bytes14 namespace = resourceId.getNamespace();
for (uint256 i; i < NAMESPACE_BYTES - 1; i++) {
if (Bytes.slice1(namespace, i) == bytes1("_") && Bytes.slice1(namespace, i + 1) == bytes1("_")) {
revert IWorldErrors.World_InvalidNamespace(namespace);
}
}
}
17 changes: 17 additions & 0 deletions packages/world/test/World.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,23 @@ contract WorldTest is Test, GasReporter {
world.registerNamespace(namespaceId);
}

function testRegisterInvalidNamespace() public {
ResourceId invalidNamespaceId = WorldResourceIdLib.encode(RESOURCE_SYSTEM, "namespace", "system");
vm.expectRevert(
abi.encodeWithSelector(
IWorldErrors.World_InvalidResourceType.selector,
RESOURCE_NAMESPACE,
invalidNamespaceId,
invalidNamespaceId.toString()
)
);
world.registerNamespace(invalidNamespaceId);

bytes14 invalidNamespace = "invld__nmsp";
vm.expectRevert(abi.encodeWithSelector(IWorldErrors.World_InvalidNamespace.selector, invalidNamespace));
world.registerNamespace(WorldResourceIdLib.encodeNamespace(invalidNamespace));
}

function testRegisterCoreNamespacesRevert() public {
vm.expectRevert(
abi.encodeWithSelector(
Expand Down
Loading