diff --git a/packages/world/gas-report.json b/packages/world/gas-report.json index f699fe79b7..0729e1730c 100644 --- a/packages/world/gas-report.json +++ b/packages/world/gas-report.json @@ -33,67 +33,67 @@ "file": "test/KeysInTableModule.t.sol", "test": "testInstallComposite", "name": "install keys in table module", - "gasUsed": 1414806 + "gasUsed": 1414938 }, { "file": "test/KeysInTableModule.t.sol", "test": "testInstallGas", "name": "install keys in table module", - "gasUsed": 1414806 + "gasUsed": 1414938 }, { "file": "test/KeysInTableModule.t.sol", "test": "testInstallGas", "name": "set a record on a table with keysInTableModule installed", - "gasUsed": 158250 + "gasUsed": 158313 }, { "file": "test/KeysInTableModule.t.sol", "test": "testInstallSingleton", "name": "install keys in table module", - "gasUsed": 1414806 + "gasUsed": 1414938 }, { "file": "test/KeysInTableModule.t.sol", "test": "testSetAndDeleteRecordHookCompositeGas", "name": "install keys in table module", - "gasUsed": 1414806 + "gasUsed": 1414938 }, { "file": "test/KeysInTableModule.t.sol", "test": "testSetAndDeleteRecordHookCompositeGas", "name": "change a composite record on a table with keysInTableModule installed", - "gasUsed": 22280 + "gasUsed": 22301 }, { "file": "test/KeysInTableModule.t.sol", "test": "testSetAndDeleteRecordHookCompositeGas", "name": "delete a composite record on a table with keysInTableModule installed", - "gasUsed": 160268 + "gasUsed": 160457 }, { "file": "test/KeysInTableModule.t.sol", "test": "testSetAndDeleteRecordHookGas", "name": "install keys in table module", - "gasUsed": 1414806 + "gasUsed": 1414938 }, { "file": "test/KeysInTableModule.t.sol", "test": "testSetAndDeleteRecordHookGas", "name": "change a record on a table with keysInTableModule installed", - "gasUsed": 21002 + "gasUsed": 21023 }, { "file": "test/KeysInTableModule.t.sol", "test": "testSetAndDeleteRecordHookGas", "name": "delete a record on a table with keysInTableModule installed", - "gasUsed": 85544 + "gasUsed": 85649 }, { "file": "test/KeysWithValueModule.t.sol", "test": "testGetKeysWithValueGas", "name": "install keys with value module", - "gasUsed": 665200 + "gasUsed": 654480 }, { "file": "test/KeysWithValueModule.t.sol", @@ -111,49 +111,49 @@ "file": "test/KeysWithValueModule.t.sol", "test": "testInstall", "name": "install keys with value module", - "gasUsed": 665200 + "gasUsed": 654480 }, { "file": "test/KeysWithValueModule.t.sol", "test": "testInstall", "name": "set a record on a table with KeysWithValueModule installed", - "gasUsed": 135628 + "gasUsed": 135691 }, { "file": "test/KeysWithValueModule.t.sol", "test": "testSetAndDeleteRecordHook", "name": "install keys with value module", - "gasUsed": 665200 + "gasUsed": 654480 }, { "file": "test/KeysWithValueModule.t.sol", "test": "testSetAndDeleteRecordHook", "name": "change a record on a table with KeysWithValueModule installed", - "gasUsed": 106008 + "gasUsed": 106071 }, { "file": "test/KeysWithValueModule.t.sol", "test": "testSetAndDeleteRecordHook", "name": "delete a record on a table with KeysWithValueModule installed", - "gasUsed": 35327 + "gasUsed": 35369 }, { "file": "test/KeysWithValueModule.t.sol", "test": "testSetField", "name": "install keys with value module", - "gasUsed": 665200 + "gasUsed": 654480 }, { "file": "test/KeysWithValueModule.t.sol", "test": "testSetField", "name": "set a field on a table with KeysWithValueModule installed", - "gasUsed": 148379 + "gasUsed": 148442 }, { "file": "test/KeysWithValueModule.t.sol", "test": "testSetField", "name": "change a field on a table with KeysWithValueModule installed", - "gasUsed": 113138 + "gasUsed": 113201 }, { "file": "test/query.t.sol", @@ -225,157 +225,157 @@ "file": "test/StandardDelegationsModule.t.sol", "test": "testCallFromCallboundDelegation", "name": "register a callbound delegation", - "gasUsed": 114571 + "gasUsed": 114592 }, { "file": "test/StandardDelegationsModule.t.sol", "test": "testCallFromCallboundDelegation", "name": "call a system via a callbound delegation", - "gasUsed": 34058 + "gasUsed": 34079 }, { "file": "test/StandardDelegationsModule.t.sol", "test": "testCallFromTimeboundDelegation", "name": "register a timebound delegation", - "gasUsed": 109041 + "gasUsed": 109062 }, { "file": "test/StandardDelegationsModule.t.sol", "test": "testCallFromTimeboundDelegation", "name": "call a system via a timebound delegation", - "gasUsed": 27106 + "gasUsed": 27127 }, { "file": "test/UniqueEntityModule.t.sol", "test": "testInstall", "name": "install unique entity module", - "gasUsed": 690323 + "gasUsed": 679566 }, { "file": "test/UniqueEntityModule.t.sol", "test": "testInstall", "name": "get a unique entity nonce (non-root module)", - "gasUsed": 52093 + "gasUsed": 52129 }, { "file": "test/UniqueEntityModule.t.sol", "test": "testInstallRoot", "name": "installRoot unique entity module", - "gasUsed": 680596 + "gasUsed": 669881 }, { "file": "test/UniqueEntityModule.t.sol", "test": "testInstallRoot", "name": "get a unique entity nonce (root module)", - "gasUsed": 52093 + "gasUsed": 52129 }, { "file": "test/World.t.sol", "test": "testCall", "name": "call a system via the World", - "gasUsed": 12725 + "gasUsed": 12746 }, { "file": "test/World.t.sol", "test": "testCallFromUnlimitedDelegation", "name": "register an unlimited delegation", - "gasUsed": 50615 + "gasUsed": 50636 }, { "file": "test/World.t.sol", "test": "testCallFromUnlimitedDelegation", "name": "call a system via an unlimited delegation", - "gasUsed": 12961 + "gasUsed": 12982 }, { "file": "test/World.t.sol", "test": "testDeleteRecord", "name": "Delete record", - "gasUsed": 9111 + "gasUsed": 9132 }, { "file": "test/World.t.sol", "test": "testPushToField", "name": "Push data to the table", - "gasUsed": 86838 + "gasUsed": 86859 }, { "file": "test/World.t.sol", "test": "testRegisterFallbackSystem", "name": "Register a fallback system", - "gasUsed": 59404 + "gasUsed": 59419 }, { "file": "test/World.t.sol", "test": "testRegisterFallbackSystem", "name": "Register a root fallback system", - "gasUsed": 52929 + "gasUsed": 52944 }, { "file": "test/World.t.sol", "test": "testRegisterFunctionSelector", "name": "Register a function selector", - "gasUsed": 79998 + "gasUsed": 80013 }, { "file": "test/World.t.sol", "test": "testRegisterNamespace", "name": "Register a new namespace", - "gasUsed": 123238 + "gasUsed": 123253 }, { "file": "test/World.t.sol", "test": "testRegisterRootFunctionSelector", "name": "Register a root function selector", - "gasUsed": 74842 + "gasUsed": 74857 }, { "file": "test/World.t.sol", "test": "testRegisterSystem", "name": "register a system", - "gasUsed": 165710 + "gasUsed": 165731 }, { "file": "test/World.t.sol", "test": "testRegisterTable", "name": "Register a new table in the namespace", - "gasUsed": 651789 + "gasUsed": 640951 }, { "file": "test/World.t.sol", "test": "testSetField", "name": "Write data to a table field", - "gasUsed": 37369 + "gasUsed": 37390 }, { "file": "test/World.t.sol", "test": "testSetRecord", "name": "Write data to the table", - "gasUsed": 35363 + "gasUsed": 36534 }, { "file": "test/WorldDynamicUpdate.t.sol", "test": "testPopFromField", "name": "pop 1 address (cold)", - "gasUsed": 24599 + "gasUsed": 24620 }, { "file": "test/WorldDynamicUpdate.t.sol", "test": "testPopFromField", "name": "pop 1 address (warm)", - "gasUsed": 13745 + "gasUsed": 13766 }, { "file": "test/WorldDynamicUpdate.t.sol", "test": "testUpdateInField", "name": "updateInField 1 item (cold)", - "gasUsed": 25204 + "gasUsed": 25225 }, { "file": "test/WorldDynamicUpdate.t.sol", "test": "testUpdateInField", "name": "updateInField 1 item (warm)", - "gasUsed": 14409 + "gasUsed": 14430 }, { "file": "test/WorldResourceId.t.sol", diff --git a/packages/world/mud.config.ts b/packages/world/mud.config.ts index 9a3fe83257..2add7cfb72 100644 --- a/packages/world/mud.config.ts +++ b/packages/world/mud.config.ts @@ -170,6 +170,15 @@ export default mudConfig({ }, tableIdArgument: true, }, + TwoFields: { + directory: "../test/tables", + keySchema: {}, + valueSchema: { + value1: "bool", + value2: "bool", + }, + tableIdArgument: true, + }, AddressArray: { directory: "../test/tables", valueSchema: "address[]", diff --git a/packages/world/src/World.sol b/packages/world/src/World.sol index f24bf892e1..f9bea13bfd 100644 --- a/packages/world/src/World.sol +++ b/packages/world/src/World.sol @@ -49,10 +49,20 @@ contract World is StoreRead, IStoreData, IWorldKernel { emit HelloWorld(WORLD_VERSION); } + /** + * Prevent the World from calling itself. + */ + modifier requireNoCallback() { + if (msg.sender == address(this)) { + revert WorldCallbackNotAllowed(msg.sig); + } + _; + } + /** * Allows the creator of the World to initialize the World once. */ - function initialize(IModule coreModule) public { + function initialize(IModule coreModule) public requireNoCallback { // Only the initial creator of the World can initialize it if (msg.sender != creator) { revert AccessDenied(ROOT_NAMESPACE_ID.toString(), msg.sender); @@ -72,12 +82,12 @@ contract World is StoreRead, IStoreData, IWorldKernel { * Requires the caller to own the root namespace. * The module is delegatecalled and installed in the root namespace. */ - function installRootModule(IModule module, bytes memory args) public { + function installRootModule(IModule module, bytes memory args) public requireNoCallback { AccessControl.requireOwner(ROOT_NAMESPACE_ID, msg.sender); _installRootModule(module, args); } - function _installRootModule(IModule module, bytes memory args) internal { + function _installRootModule(IModule module, bytes memory args) internal requireNoCallback { // Require the provided address to implement the IModule interface requireInterface(address(module), MODULE_INTERFACE_ID); @@ -109,7 +119,7 @@ contract World is StoreRead, IStoreData, IWorldKernel { PackedCounter encodedLengths, bytes calldata dynamicData, FieldLayout fieldLayout - ) public virtual { + ) public virtual requireNoCallback { // Require access to the namespace or name AccessControl.requireAccess(tableId, msg.sender); @@ -123,7 +133,7 @@ contract World is StoreRead, IStoreData, IWorldKernel { uint48 start, uint40 deleteCount, bytes calldata data - ) public virtual { + ) public virtual requireNoCallback { // Require access to the namespace or name AccessControl.requireAccess(tableId, msg.sender); @@ -138,7 +148,7 @@ contract World is StoreRead, IStoreData, IWorldKernel { uint40 startWithinField, uint40 deleteCount, bytes calldata data - ) public virtual { + ) public virtual requireNoCallback { // Require access to the namespace or name AccessControl.requireAccess(tableId, msg.sender); @@ -156,7 +166,7 @@ contract World is StoreRead, IStoreData, IWorldKernel { uint8 fieldIndex, bytes calldata data, FieldLayout fieldLayout - ) public virtual { + ) public virtual requireNoCallback { // Require access to namespace or name AccessControl.requireAccess(tableId, msg.sender); @@ -174,7 +184,7 @@ contract World is StoreRead, IStoreData, IWorldKernel { uint8 fieldIndex, bytes calldata dataToPush, FieldLayout fieldLayout - ) public virtual { + ) public virtual requireNoCallback { // Require access to namespace or name AccessControl.requireAccess(tableId, msg.sender); @@ -192,7 +202,7 @@ contract World is StoreRead, IStoreData, IWorldKernel { uint8 fieldIndex, uint256 byteLengthToPop, FieldLayout fieldLayout - ) public virtual { + ) public virtual requireNoCallback { // Require access to namespace or name AccessControl.requireAccess(tableId, msg.sender); @@ -211,7 +221,7 @@ contract World is StoreRead, IStoreData, IWorldKernel { uint256 startByteIndex, bytes calldata dataToSet, FieldLayout fieldLayout - ) public virtual { + ) public virtual requireNoCallback { // Require access to namespace or name AccessControl.requireAccess(tableId, msg.sender); @@ -223,7 +233,11 @@ contract World is StoreRead, IStoreData, IWorldKernel { * Delete a record in the table at the given tableId. * Requires the caller to have access to the namespace or name. */ - function deleteRecord(ResourceId tableId, bytes32[] calldata keyTuple, FieldLayout fieldLayout) public virtual { + function deleteRecord( + ResourceId tableId, + bytes32[] calldata keyTuple, + FieldLayout fieldLayout + ) public virtual requireNoCallback { // Require access to namespace or name AccessControl.requireAccess(tableId, msg.sender); @@ -241,7 +255,10 @@ contract World is StoreRead, IStoreData, IWorldKernel { * Call the system at the given system ID. * If the system is not public, the caller must have access to the namespace or name (encoded in the system ID). */ - function call(ResourceId systemId, bytes memory callData) external payable virtual returns (bytes memory) { + function call( + ResourceId systemId, + bytes memory callData + ) external payable virtual requireNoCallback returns (bytes memory) { return SystemCall.callWithHooksOrRevert(msg.sender, systemId, callData, msg.value); } @@ -253,7 +270,7 @@ contract World is StoreRead, IStoreData, IWorldKernel { address delegator, ResourceId systemId, bytes memory callData - ) external payable virtual returns (bytes memory) { + ) external payable virtual requireNoCallback returns (bytes memory) { // If the delegator is the caller, call the system directly if (delegator == msg.sender) { return SystemCall.callWithHooksOrRevert(msg.sender, systemId, callData, msg.value); diff --git a/packages/world/src/index.sol b/packages/world/src/index.sol index 2e852c4973..d29dc778c0 100644 --- a/packages/world/src/index.sol +++ b/packages/world/src/index.sol @@ -19,4 +19,5 @@ import { UniqueEntity } from "./modules/uniqueentity/tables/UniqueEntity.sol"; import { CallboundDelegations, CallboundDelegationsTableId } from "./modules/std-delegations/tables/CallboundDelegations.sol"; import { TimeboundDelegations, TimeboundDelegationsTableId } from "./modules/std-delegations/tables/TimeboundDelegations.sol"; import { Bool } from "./../test/tables/Bool.sol"; +import { TwoFields, TwoFieldsData } from "./../test/tables/TwoFields.sol"; import { AddressArray } from "./../test/tables/AddressArray.sol"; diff --git a/packages/world/src/interfaces/IWorldErrors.sol b/packages/world/src/interfaces/IWorldErrors.sol index ef421ff6b7..56a26e48ca 100644 --- a/packages/world/src/interfaces/IWorldErrors.sol +++ b/packages/world/src/interfaces/IWorldErrors.sol @@ -16,4 +16,5 @@ interface IWorldErrors { error InsufficientBalance(uint256 balance, uint256 amount); error InterfaceNotSupported(address contractAddress, bytes4 interfaceId); error InvalidResourceType(bytes2 expected, ResourceId resourceId, string resourceIdString); + error WorldCallbackNotAllowed(bytes4 functionSelector); } diff --git a/packages/world/src/modules/core/CoreModule.sol b/packages/world/src/modules/core/CoreModule.sol index 9d5fc3a852..48873dac3c 100644 --- a/packages/world/src/modules/core/CoreModule.sol +++ b/packages/world/src/modules/core/CoreModule.sol @@ -18,7 +18,7 @@ import { InstalledModules } from "../../tables/InstalledModules.sol"; import { Delegations } from "../../tables/Delegations.sol"; import { CoreSystem } from "./CoreSystem.sol"; -import { CORE_MODULE_NAME, CORE_SYSTEM_NAME } from "./constants.sol"; +import { CORE_MODULE_NAME, CORE_SYSTEM_ID } from "./constants.sol"; import { Systems } from "./tables/Systems.sol"; import { FunctionSelectors } from "./tables/FunctionSelectors.sol"; @@ -87,14 +87,7 @@ contract CoreModule is Module { msgSender: _msgSender(), msgValue: 0, target: coreSystem, - callData: abi.encodeCall( - WorldRegistrationSystem.registerSystem, - ( - WorldResourceIdLib.encode({ typeId: RESOURCE_SYSTEM, namespace: ROOT_NAMESPACE, name: CORE_SYSTEM_NAME }), - CoreSystem(coreSystem), - true - ) - ) + callData: abi.encodeCall(WorldRegistrationSystem.registerSystem, (CORE_SYSTEM_ID, CoreSystem(coreSystem), true)) }); } @@ -135,11 +128,7 @@ contract CoreModule is Module { target: coreSystem, callData: abi.encodeCall( WorldRegistrationSystem.registerRootFunctionSelector, - ( - WorldResourceIdLib.encode({ typeId: RESOURCE_SYSTEM, namespace: ROOT_NAMESPACE, name: CORE_SYSTEM_NAME }), - functionSelectors[i], - functionSelectors[i] - ) + (CORE_SYSTEM_ID, functionSelectors[i], functionSelectors[i]) ) }); } diff --git a/packages/world/src/modules/core/constants.sol b/packages/world/src/modules/core/constants.sol index 5b6a6eb734..e2a6405263 100644 --- a/packages/world/src/modules/core/constants.sol +++ b/packages/world/src/modules/core/constants.sol @@ -1,5 +1,14 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.0; +import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; + +import { ROOT_NAMESPACE } from "../../constants.sol"; +import { RESOURCE_SYSTEM } from "../../worldResourceTypes.sol"; + bytes16 constant CORE_MODULE_NAME = bytes16("core.m"); bytes16 constant CORE_SYSTEM_NAME = bytes16("core.s"); + +ResourceId constant CORE_SYSTEM_ID = ResourceId.wrap( + bytes32(abi.encodePacked(RESOURCE_SYSTEM, ROOT_NAMESPACE, CORE_SYSTEM_NAME)) +); diff --git a/packages/world/src/modules/core/implementations/StoreRegistrationSystem.sol b/packages/world/src/modules/core/implementations/StoreRegistrationSystem.sol index b86a42fabe..bf3fa92bc6 100644 --- a/packages/world/src/modules/core/implementations/StoreRegistrationSystem.sol +++ b/packages/world/src/modules/core/implementations/StoreRegistrationSystem.sol @@ -23,7 +23,7 @@ import { SystemRegistry } from "../tables/SystemRegistry.sol"; import { Systems } from "../tables/Systems.sol"; import { FunctionSelectors } from "../tables/FunctionSelectors.sol"; -import { CORE_SYSTEM_NAME } from "../constants.sol"; +import { CORE_SYSTEM_ID } from "../constants.sol"; import { WorldRegistrationSystem } from "./WorldRegistrationSystem.sol"; @@ -52,7 +52,8 @@ contract StoreRegistrationSystem is System, IWorldErrors { if (!ResourceIds._getExists(ResourceId.unwrap(namespaceId))) { // Since this is a root system, we're in the context of the World contract already, // so we can use delegatecall to register the namespace - (bool success, bytes memory data) = address(this).delegatecall( + (address coreSystemAddress, ) = Systems._get(ResourceId.unwrap(CORE_SYSTEM_ID)); + (bool success, bytes memory data) = coreSystemAddress.delegatecall( abi.encodeCall(WorldRegistrationSystem.registerNamespace, (namespaceId)) ); if (!success) revertWithBytes(data); diff --git a/packages/world/test/World.t.sol b/packages/world/test/World.t.sol index 05ca6fe870..0410d64fb2 100644 --- a/packages/world/test/World.t.sol +++ b/packages/world/test/World.t.sol @@ -44,6 +44,7 @@ import { IWorldErrors } from "../src/interfaces/IWorldErrors.sol"; import { ISystemHook, SYSTEM_HOOK_INTERFACE_ID } from "../src/interfaces/ISystemHook.sol"; import { Bool } from "./tables/Bool.sol"; +import { TwoFields, TwoFieldsData } from "./tables/TwoFields.sol"; import { AddressArray } from "./tables/AddressArray.sol"; interface IWorldTestSystem { @@ -689,27 +690,30 @@ contract WorldTest is Test, GasReporter { // Register a new table world.registerTable( tableId, - Bool.getFieldLayout(), - defaultKeySchema, - Bool.getValueSchema(), - new string[](1), - new string[](1) + TwoFields.getFieldLayout(), + TwoFields.getKeySchema(), + TwoFields.getValueSchema(), + new string[](0), + new string[](2) ); startGasReport("Write data to the table"); - Bool.set(world, tableId, true); + TwoFields.set(world, tableId, true, true); endGasReport(); // Expect the data to be written - assertTrue(Bool.get(world, tableId)); + TwoFieldsData memory tableData = (TwoFields.get(world, tableId)); + assertTrue(tableData.value1); + assertTrue(tableData.value2); // Expect an error when trying to write from an address that doesn't have access _expectAccessDenied(address(0x01), "testSetRecord", "testTable", RESOURCE_TABLE); - Bool.set(world, tableId, true); + TwoFields.set(world, tableId, true, true); // Expect the World to not have access - _expectAccessDenied(address(world), "testSetRecord", "testTable", RESOURCE_TABLE); - Bool.set(world, tableId, true); + vm.prank(address(world)); + vm.expectRevert(abi.encodeWithSelector(IWorldErrors.WorldCallbackNotAllowed.selector, world.setRecord.selector)); + TwoFields.set(world, tableId, true, true); } function testSetField() public { @@ -734,7 +738,8 @@ contract WorldTest is Test, GasReporter { world.setField(tableId, singletonKey, 0, abi.encodePacked(true), fieldLayout); // Expect the World to not have access - _expectAccessDenied(address(world), "testSetField", "testTable", RESOURCE_TABLE); + vm.prank(address(world)); + vm.expectRevert(abi.encodeWithSelector(IWorldErrors.WorldCallbackNotAllowed.selector, world.setField.selector)); world.setField(tableId, singletonKey, 0, abi.encodePacked(true), fieldLayout); } @@ -776,7 +781,8 @@ contract WorldTest is Test, GasReporter { world.pushToField(tableId, keyTuple, 0, encodedData, fieldLayout); // Expect the World to not have access - _expectAccessDenied(address(world), namespace, name, RESOURCE_TABLE); + vm.prank(address(world)); + vm.expectRevert(abi.encodeWithSelector(IWorldErrors.WorldCallbackNotAllowed.selector, world.pushToField.selector)); world.pushToField(tableId, keyTuple, 0, encodedData, fieldLayout); } @@ -825,7 +831,8 @@ contract WorldTest is Test, GasReporter { world.deleteRecord(tableId, singletonKey, fieldLayout); // Expect the World to not have access - _expectAccessDenied(address(world), namespace, name, RESOURCE_TABLE); + vm.prank(address(world)); + vm.expectRevert(abi.encodeWithSelector(IWorldErrors.WorldCallbackNotAllowed.selector, world.deleteRecord.selector)); world.deleteRecord(tableId, singletonKey, fieldLayout); } @@ -865,7 +872,8 @@ contract WorldTest is Test, GasReporter { world.call(systemId, abi.encodeCall(WorldTestSystem.msgSender, ())); // Expect the World to have not access - _expectAccessDenied(address(world), "namespace", "testSystem", RESOURCE_SYSTEM); + vm.prank(address(world)); + vm.expectRevert(abi.encodeWithSelector(IWorldErrors.WorldCallbackNotAllowed.selector, world.call.selector)); world.call(systemId, abi.encodeCall(WorldTestSystem.msgSender, ())); // Expect errors from the system to be forwarded diff --git a/packages/world/test/WorldDynamicUpdate.t.sol b/packages/world/test/WorldDynamicUpdate.t.sol index f7f8a0840f..f2a725c857 100644 --- a/packages/world/test/WorldDynamicUpdate.t.sol +++ b/packages/world/test/WorldDynamicUpdate.t.sol @@ -131,8 +131,9 @@ contract UpdateInFieldTest is Test, GasReporter { _expectAccessDenied(address(0x01), tableId); world.popFromField(tableId, keyTuple, 0, 20, fieldLayout); - // Expect the World to have access - _expectAccessDenied(address(world), tableId); + // Expect the World to not have access + vm.prank(address(world)); + vm.expectRevert(abi.encodeWithSelector(IWorldErrors.WorldCallbackNotAllowed.selector, world.popFromField.selector)); world.popFromField(tableId, keyTuple, 0, 20, fieldLayout); } @@ -169,8 +170,11 @@ contract UpdateInFieldTest is Test, GasReporter { _expectAccessDenied(address(0x01), tableId); world.updateInField(tableId, keyTuple, 0, 0, EncodeArray.encode(dataForUpdate), fieldLayout); - // Expect the World to have access - _expectAccessDenied(address(world), tableId); + // Expect the World to not have access + vm.prank(address(world)); + vm.expectRevert( + abi.encodeWithSelector(IWorldErrors.WorldCallbackNotAllowed.selector, world.updateInField.selector) + ); world.updateInField(tableId, keyTuple, 0, 0, EncodeArray.encode(dataForUpdate), fieldLayout); } } diff --git a/packages/world/test/tables/TwoFields.sol b/packages/world/test/tables/TwoFields.sol new file mode 100644 index 0000000000..c257dd902e --- /dev/null +++ b/packages/world/test/tables/TwoFields.sol @@ -0,0 +1,328 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +/* Autogenerated file. Do not edit manually. */ + +// Import schema type +import { SchemaType } from "@latticexyz/schema-type/src/solidity/SchemaType.sol"; + +// Import store internals +import { IStore } from "@latticexyz/store/src/IStore.sol"; +import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol"; +import { StoreCore } from "@latticexyz/store/src/StoreCore.sol"; +import { Bytes } from "@latticexyz/store/src/Bytes.sol"; +import { Memory } from "@latticexyz/store/src/Memory.sol"; +import { SliceLib } from "@latticexyz/store/src/Slice.sol"; +import { EncodeArray } from "@latticexyz/store/src/tightcoder/EncodeArray.sol"; +import { FieldLayout, FieldLayoutLib } from "@latticexyz/store/src/FieldLayout.sol"; +import { Schema, SchemaLib } from "@latticexyz/store/src/Schema.sol"; +import { PackedCounter, PackedCounterLib } from "@latticexyz/store/src/PackedCounter.sol"; +import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; +import { RESOURCE_TABLE, RESOURCE_OFFCHAIN_TABLE } from "@latticexyz/store/src/storeResourceTypes.sol"; + +FieldLayout constant _fieldLayout = FieldLayout.wrap( + 0x0002020001010000000000000000000000000000000000000000000000000000 +); + +struct TwoFieldsData { + bool value1; + bool value2; +} + +library TwoFields { + /** Get the table values' field layout */ + function getFieldLayout() internal pure returns (FieldLayout) { + return _fieldLayout; + } + + /** Get the table's key schema */ + function getKeySchema() internal pure returns (Schema) { + SchemaType[] memory _keySchema = new SchemaType[](0); + + return SchemaLib.encode(_keySchema); + } + + /** Get the table's value schema */ + function getValueSchema() internal pure returns (Schema) { + SchemaType[] memory _valueSchema = new SchemaType[](2); + _valueSchema[0] = SchemaType.BOOL; + _valueSchema[1] = SchemaType.BOOL; + + return SchemaLib.encode(_valueSchema); + } + + /** Get the table's key names */ + function getKeyNames() internal pure returns (string[] memory keyNames) { + keyNames = new string[](0); + } + + /** Get the table's field names */ + function getFieldNames() internal pure returns (string[] memory fieldNames) { + fieldNames = new string[](2); + fieldNames[0] = "value1"; + fieldNames[1] = "value2"; + } + + /** Register the table with its config */ + function register(ResourceId _tableId) internal { + StoreSwitch.registerTable(_tableId, _fieldLayout, getKeySchema(), getValueSchema(), getKeyNames(), getFieldNames()); + } + + /** Register the table with its config */ + function _register(ResourceId _tableId) internal { + StoreCore.registerTable(_tableId, _fieldLayout, getKeySchema(), getValueSchema(), getKeyNames(), getFieldNames()); + } + + /** Register the table with its config (using the specified store) */ + function register(IStore _store, ResourceId _tableId) internal { + _store.registerTable(_tableId, _fieldLayout, getKeySchema(), getValueSchema(), getKeyNames(), getFieldNames()); + } + + /** Get value1 */ + function getValue1(ResourceId _tableId) internal view returns (bool value1) { + bytes32[] memory _keyTuple = new bytes32[](0); + + bytes32 _blob = StoreSwitch.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (_toBool(uint8(bytes1(_blob)))); + } + + /** Get value1 */ + function _getValue1(ResourceId _tableId) internal view returns (bool value1) { + bytes32[] memory _keyTuple = new bytes32[](0); + + bytes32 _blob = StoreCore.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (_toBool(uint8(bytes1(_blob)))); + } + + /** Get value1 (using the specified store) */ + function getValue1(IStore _store, ResourceId _tableId) internal view returns (bool value1) { + bytes32[] memory _keyTuple = new bytes32[](0); + + bytes32 _blob = _store.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (_toBool(uint8(bytes1(_blob)))); + } + + /** Set value1 */ + function setValue1(ResourceId _tableId, bool value1) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreSwitch.setField(_tableId, _keyTuple, 0, abi.encodePacked((value1)), _fieldLayout); + } + + /** Set value1 */ + function _setValue1(ResourceId _tableId, bool value1) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreCore.setField(_tableId, _keyTuple, 0, abi.encodePacked((value1)), _fieldLayout); + } + + /** Set value1 (using the specified store) */ + function setValue1(IStore _store, ResourceId _tableId, bool value1) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + _store.setField(_tableId, _keyTuple, 0, abi.encodePacked((value1)), _fieldLayout); + } + + /** Get value2 */ + function getValue2(ResourceId _tableId) internal view returns (bool value2) { + bytes32[] memory _keyTuple = new bytes32[](0); + + bytes32 _blob = StoreSwitch.getStaticField(_tableId, _keyTuple, 1, _fieldLayout); + return (_toBool(uint8(bytes1(_blob)))); + } + + /** Get value2 */ + function _getValue2(ResourceId _tableId) internal view returns (bool value2) { + bytes32[] memory _keyTuple = new bytes32[](0); + + bytes32 _blob = StoreCore.getStaticField(_tableId, _keyTuple, 1, _fieldLayout); + return (_toBool(uint8(bytes1(_blob)))); + } + + /** Get value2 (using the specified store) */ + function getValue2(IStore _store, ResourceId _tableId) internal view returns (bool value2) { + bytes32[] memory _keyTuple = new bytes32[](0); + + bytes32 _blob = _store.getStaticField(_tableId, _keyTuple, 1, _fieldLayout); + return (_toBool(uint8(bytes1(_blob)))); + } + + /** Set value2 */ + function setValue2(ResourceId _tableId, bool value2) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreSwitch.setField(_tableId, _keyTuple, 1, abi.encodePacked((value2)), _fieldLayout); + } + + /** Set value2 */ + function _setValue2(ResourceId _tableId, bool value2) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreCore.setField(_tableId, _keyTuple, 1, abi.encodePacked((value2)), _fieldLayout); + } + + /** Set value2 (using the specified store) */ + function setValue2(IStore _store, ResourceId _tableId, bool value2) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + _store.setField(_tableId, _keyTuple, 1, abi.encodePacked((value2)), _fieldLayout); + } + + /** Get the full data */ + function get(ResourceId _tableId) internal view returns (TwoFieldsData memory _table) { + bytes32[] memory _keyTuple = new bytes32[](0); + + (bytes memory _staticData, PackedCounter _encodedLengths, bytes memory _dynamicData) = StoreSwitch.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); + } + + /** Get the full data */ + function _get(ResourceId _tableId) internal view returns (TwoFieldsData memory _table) { + bytes32[] memory _keyTuple = new bytes32[](0); + + (bytes memory _staticData, PackedCounter _encodedLengths, bytes memory _dynamicData) = StoreCore.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); + } + + /** Get the full data (using the specified store) */ + function get(IStore _store, ResourceId _tableId) internal view returns (TwoFieldsData memory _table) { + bytes32[] memory _keyTuple = new bytes32[](0); + + (bytes memory _staticData, PackedCounter _encodedLengths, bytes memory _dynamicData) = _store.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); + } + + /** Set the full data using individual values */ + function set(ResourceId _tableId, bool value1, bool value2) internal { + bytes memory _staticData = encodeStatic(value1, value2); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; + + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreSwitch.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, _fieldLayout); + } + + /** Set the full data using individual values */ + function _set(ResourceId _tableId, bool value1, bool value2) internal { + bytes memory _staticData = encodeStatic(value1, value2); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; + + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreCore.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, _fieldLayout); + } + + /** Set the full data using individual values (using the specified store) */ + function set(IStore _store, ResourceId _tableId, bool value1, bool value2) internal { + bytes memory _staticData = encodeStatic(value1, value2); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; + + bytes32[] memory _keyTuple = new bytes32[](0); + + _store.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, _fieldLayout); + } + + /** Set the full data using the data struct */ + function set(ResourceId _tableId, TwoFieldsData memory _table) internal { + set(_tableId, _table.value1, _table.value2); + } + + /** Set the full data using the data struct */ + function _set(ResourceId _tableId, TwoFieldsData memory _table) internal { + set(_tableId, _table.value1, _table.value2); + } + + /** Set the full data using the data struct (using the specified store) */ + function set(IStore _store, ResourceId _tableId, TwoFieldsData memory _table) internal { + set(_store, _tableId, _table.value1, _table.value2); + } + + /** + * Decode the tightly packed blob of static data using this table's field layout + * Undefined behaviour for invalid blobs + */ + function decodeStatic(bytes memory _blob) internal pure returns (bool value1, bool value2) { + value1 = (_toBool(uint8(Bytes.slice1(_blob, 0)))); + + value2 = (_toBool(uint8(Bytes.slice1(_blob, 1)))); + } + + /** + * Decode the tightly packed blob using this table's field layout. + * Undefined behaviour for invalid blobs. + */ + function decode( + bytes memory _staticData, + PackedCounter, + bytes memory + ) internal pure returns (TwoFieldsData memory _table) { + (_table.value1, _table.value2) = decodeStatic(_staticData); + } + + /** Delete all data for given keys */ + function deleteRecord(ResourceId _tableId) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreSwitch.deleteRecord(_tableId, _keyTuple, _fieldLayout); + } + + /** Delete all data for given keys */ + function _deleteRecord(ResourceId _tableId) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreCore.deleteRecord(_tableId, _keyTuple, _fieldLayout); + } + + /** Delete all data for given keys (using the specified store) */ + function deleteRecord(IStore _store, ResourceId _tableId) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + _store.deleteRecord(_tableId, _keyTuple, _fieldLayout); + } + + /** Tightly pack static data using this table's schema */ + function encodeStatic(bool value1, bool value2) internal pure returns (bytes memory) { + return abi.encodePacked(value1, value2); + } + + /** Tightly pack full data using this table's field layout */ + function encode(bool value1, bool value2) internal pure returns (bytes memory, PackedCounter, bytes memory) { + bytes memory _staticData = encodeStatic(value1, value2); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; + + return (_staticData, _encodedLengths, _dynamicData); + } + + /** Encode keys as a bytes32 array using this table's field layout */ + function encodeKeyTuple() internal pure returns (bytes32[] memory) { + bytes32[] memory _keyTuple = new bytes32[](0); + + return _keyTuple; + } +} + +function _toBool(uint8 value) pure returns (bool result) { + assembly { + result := value + } +}