Skip to content

Commit

Permalink
feat(store,world): add ResourceType, ResourceId
Browse files Browse the repository at this point in the history
  • Loading branch information
alvrs committed Sep 19, 2023
1 parent d5094a2 commit 436f814
Show file tree
Hide file tree
Showing 58 changed files with 1,028 additions and 844 deletions.
16 changes: 5 additions & 11 deletions examples/minimal/packages/contracts/script/PostDeploy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ pragma solidity >=0.8.0;

import { Script } from "forge-std/Script.sol";
import { console } from "forge-std/console.sol";
import { ResourceSelector } from "@latticexyz/world/src/ResourceSelector.sol";
import { ResourceId } from "@latticexyz/world/src/ResourceId.sol";
import { RESOURCE_SYSTEM } from "@latticexyz/world/src/worldResourceTypes.sol";
import { IWorld } from "../src/codegen/world/IWorld.sol";

import { MessageTable, MessageTableTableId } from "../src/codegen/index.sol";
Expand All @@ -19,16 +20,9 @@ contract PostDeploy is Script {

// Manually deploy a system with another namespace
ChatNamespacedSystem chatNamespacedSystem = new ChatNamespacedSystem();
IWorld(worldAddress).registerSystem(
ResourceSelector.from("namespace", "ChatNamespaced"),
chatNamespacedSystem,
true
);
IWorld(worldAddress).registerFunctionSelector(
ResourceSelector.from("namespace", "ChatNamespaced"),
"sendMessage",
"(string)"
);
bytes32 systemId = ResourceId.encode("namespace", "ChatNamespaced", RESOURCE_SYSTEM);
IWorld(worldAddress).registerSystem(systemId, chatNamespacedSystem, true);
IWorld(worldAddress).registerFunctionSelector(systemId, "sendMessage", "(string)");
// Grant this system access to MessageTable
IWorld(worldAddress).grantAccess(MessageTableTableId, address(chatNamespacedSystem));

Expand Down
16 changes: 16 additions & 0 deletions packages/store/src/ResourceType.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

library ResourceType {
function encode(bytes30 resourceName, bytes2 resourceType) internal pure returns (bytes32) {
return bytes32(resourceName) | (bytes32(resourceType) >> (8 * 30));
}

function getType(bytes32 resourceId) internal pure returns (bytes2) {
return bytes2(resourceId << (30 * 8));
}

function isType(bytes32 resourceId, bytes2 resourceType) internal pure returns (bool) {
return resourceId & bytes32(resourceType >> (30 * 8)) == bytes32(resourceType >> (30 * 8));
}
}
5 changes: 5 additions & 0 deletions packages/store/src/storeResourceTypes.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

bytes2 constant RESOURCE_TABLE = "ta";
bytes2 constant RESOURCE_OFFCHAIN_TABLE = "of";
44 changes: 44 additions & 0 deletions packages/store/test/ResourceType.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

import { Test, console } from "forge-std/Test.sol";
import { GasReporter } from "@latticexyz/gas-report/src/GasReporter.sol";
import { ResourceType } from "../src/ResourceType.sol";
import { RESOURCE_TABLE } from "../src/storeResourceTypes.sol";

contract ResourceTypeTest is Test, GasReporter {
using ResourceType for bytes32;

function testEncode() public {
startGasReport("encode table ID with name and type");
bytes32 tableId = ResourceType.encode("name", RESOURCE_TABLE);
endGasReport();

assertEq(tableId, bytes32(abi.encodePacked(bytes30("name"), RESOURCE_TABLE)));
}

function getGetType() public {
bytes32 tableId = ResourceType.encode("name", "tb");

startGasReport("get type from a table ID");
bytes2 resourceType = tableId.getType();
endGasReport();

assertEq(resourceType, "tb");
}

function testIsType() public {
bytes32 tableId = ResourceType.encode("name", RESOURCE_TABLE);

startGasReport("check if a table ID is of type tb");
bool isType = tableId.isType(RESOURCE_TABLE);
endGasReport();

assertTrue(isType);
}

function testFuzz(bytes30 name, bytes2 resourceType) public {
bytes32 tableId = ResourceType.encode(name, resourceType);
assertEq(tableId.getType(), resourceType);
}
}
14 changes: 7 additions & 7 deletions packages/world/mud.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export default mudConfig({
},
ResourceAccess: {
keySchema: {
resourceSelector: "bytes32",
resourceId: "bytes32",
caller: "address",
},
valueSchema: {
Expand Down Expand Up @@ -63,7 +63,7 @@ export default mudConfig({
Systems: {
directory: "modules/core/tables",
keySchema: {
resourceSelector: "bytes32",
systemId: "bytes32",
},
valueSchema: {
system: "address",
Expand All @@ -77,20 +77,20 @@ export default mudConfig({
system: "address",
},
valueSchema: {
resourceSelector: "bytes32",
systemId: "bytes32",
},
},
SystemHooks: {
directory: "modules/core/tables",
keySchema: {
resourceSelector: "bytes32",
systemId: "bytes32",
},
valueSchema: "bytes21[]",
},
ResourceType: {
directory: "modules/core/tables",
keySchema: {
resourceSelector: "bytes32",
systemId: "bytes32",
},
valueSchema: {
resourceType: "Resource",
Expand All @@ -102,7 +102,7 @@ export default mudConfig({
functionSelector: "bytes4",
},
valueSchema: {
resourceSelector: "bytes32",
systemId: "bytes32",
systemFunctionSelector: "bytes4",
},
dataStruct: false,
Expand Down Expand Up @@ -149,7 +149,7 @@ export default mudConfig({
keySchema: {
delegator: "address",
delegatee: "address",
resourceSelector: "bytes32",
systemId: "bytes32",
callDataHash: "bytes32",
},
valueSchema: {
Expand Down
26 changes: 13 additions & 13 deletions packages/world/src/AccessControl.sol
Original file line number Diff line number Diff line change
@@ -1,43 +1,43 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

import { ResourceSelector } from "./ResourceSelector.sol";
import { ResourceId } from "./ResourceId.sol";
import { IWorldErrors } from "./interfaces/IWorldErrors.sol";

import { ResourceAccess } from "./tables/ResourceAccess.sol";
import { NamespaceOwner } from "./tables/NamespaceOwner.sol";

library AccessControl {
using ResourceSelector for bytes32;
using ResourceId for bytes32;

/**
* Returns true if the caller has access to the namespace or name, false otherwise.
*/
function hasAccess(bytes32 resourceSelector, address caller) internal view returns (bool) {
function hasAccess(bytes32 resourceId, address caller) internal view returns (bool) {
return
address(this) == caller || // First check if the World is calling itself
ResourceAccess._get(resourceSelector.getNamespace(), caller) || // Then check access based on the namespace
ResourceAccess._get(resourceSelector, caller); // If caller has no namespace access, check access on the name
address(this) == caller || // First check if the World is calling itself // TODO: remove this check?
ResourceAccess._get(resourceId.getNamespaceId(), caller) || // Then check access based on the namespace
ResourceAccess._get(resourceId, caller); // If caller has no namespace access, check access on the name
}

/**
* Check for access at the given namespace or name.
* Reverts with AccessDenied if the caller has no access.
*/
function requireAccess(bytes32 resourceSelector, address caller) internal view {
function requireAccess(bytes32 resourceId, address caller) internal view {
// Check if the given caller has access to the given namespace or name
if (!hasAccess(resourceSelector, caller)) {
revert IWorldErrors.AccessDenied(resourceSelector.toString(), caller);
if (!hasAccess(resourceId, caller)) {
revert IWorldErrors.AccessDenied(resourceId.toString(), caller);
}
}

/**
* Check for ownership of the namespace of the given resource selector.
* Check for ownership of the namespace of the given resource ID.
* Reverts with AccessDenied if the check fails.
*/
function requireOwner(bytes32 resourceSelector, address caller) internal view {
if (NamespaceOwner._get(resourceSelector.getNamespace()) != caller) {
revert IWorldErrors.AccessDenied(resourceSelector.toString(), caller);
function requireOwner(bytes32 resourceId, address caller) internal view {
if (NamespaceOwner._get(resourceId.getNamespace()) != caller) {
revert IWorldErrors.AccessDenied(resourceId.toString(), caller);
}
}
}
2 changes: 1 addition & 1 deletion packages/world/src/Delegation.sol
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ library DelegationInstance {
// Call the delegation control contract to check if the delegator has granted access to the delegatee
(bool success, bytes memory data) = SystemCall.call({
caller: delegatee,
resourceSelector: Delegation.unwrap(self),
systemId: Delegation.unwrap(self),
callData: abi.encodeCall(IDelegationControl.verify, (delegator, systemId, callData)),
value: 0
});
Expand Down
92 changes: 92 additions & 0 deletions packages/world/src/ResourceId.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
import { ROOT_NAMESPACE, ROOT_NAME } from "./constants.sol";
import { Bytes } from "@latticexyz/store/src/Bytes.sol";
import { ResourceType } from "@latticexyz/store/src/ResourceType.sol";
import { RESOURCE_NAMESPACE, MASK_RESOURCE_NAMESPACE } from "./worldResourceTypes.sol";

bytes16 constant ROOT_NAMESPACE_STRING = bytes16("ROOT_NAMESPACE");
bytes16 constant ROOT_NAME_STRING = bytes16("ROOT_NAME");

bytes32 constant NAMESPACE_MASK = bytes32(~bytes14(""));

// TODO: change all namespace functions to namespace id functions (don't use bytes14 but bytes32 for ownership and access control)
// TODO: should resource id be a user type to avoid type errors of eg passing a namespace (bytes14) instead of a namespace id (bytes32)
// (I ran into a bunch of runtime errors while refactoring this where bytes14 was automatically casted to bytes32)

library ResourceId {
/**
* Create a 32-byte resource ID from a namespace, name and type.
*
* A resource ID is a 32-byte value that uniquely identifies a resource.
* The first 14 bytes represent the namespace,
* the next 16 bytes represent the name,
* the last 2 bytes represent the type.
*
* Note: the location of the resource type needs to stay aligned with `ResourceType`
*/
function encode(
bytes14 resourceNamespace,
bytes16 resourceName,
bytes2 resourceType
) internal pure returns (bytes32) {
return bytes32(resourceNamespace) | ((bytes32(resourceName) >> (14 * 8)) | (bytes32(resourceType) >> (30 * 8)));
}

/**
* Create a 32-byte resource ID from a namespace.
*/
function encodeNamespace(bytes14 resourceNamespace) internal pure returns (bytes32) {
return bytes32(resourceNamespace) | (bytes32(RESOURCE_NAMESPACE) >> (30 * 8));
}

/**
* Get the namespace of a resource ID.
*/
function getNamespace(bytes32 resourceId) internal pure returns (bytes14) {
return bytes14(resourceId);
}

/**
* Get the namespace resource ID corresponding to the namespace of a resource ID.
*/
function getNamespaceId(bytes32 resourceId) internal pure returns (bytes32) {
return (resourceId & NAMESPACE_MASK) | MASK_RESOURCE_NAMESPACE;
}

/**
* Get the name of a resource ID.
*/
function getName(bytes32 resourceId) internal pure returns (bytes16) {
return bytes16(resourceId << (14 * 8));
}

/**
* Convert a resource ID to a string for more readable logs
*/
function toString(bytes32 resourceId) internal pure returns (string memory) {
bytes14 resourceNamespace = getNamespace(resourceId);
bytes16 resourceName = getName(resourceId);
bytes2 resourceType = ResourceType.getType(resourceId);
return
string(
abi.encodePacked(
resourceNamespace == ROOT_NAMESPACE ? ROOT_NAMESPACE_STRING : resourceNamespace,
"_",
resourceName == ROOT_NAME ? ROOT_NAME_STRING : resourceName,
".",
resourceType
)
);
}

/**
* Convert a selector to a trimmed string (no trailing `null` ASCII characters)
*/
function toTrimmedString(bytes16 paddedString) internal pure returns (string memory) {
uint256 length;
for (; length < 16; length++) if (Bytes.slice1(paddedString, length) == 0) break;
bytes memory packedSelector = abi.encodePacked(paddedString);
return string(Bytes.setLength(packedSelector, length));
}
}
66 changes: 0 additions & 66 deletions packages/world/src/ResourceSelector.sol

This file was deleted.

Loading

0 comments on commit 436f814

Please sign in to comment.