diff --git a/.changeset/light-carrots-applaud.md b/.changeset/light-carrots-applaud.md new file mode 100644 index 0000000000..fc90e67cbb --- /dev/null +++ b/.changeset/light-carrots-applaud.md @@ -0,0 +1,5 @@ +--- +"@latticexyz/world-modules": minor +--- + +Fixed `SystemSwitch` to properly call non-root systems from root systems. diff --git a/packages/world-modules/src/utils/SystemSwitch.sol b/packages/world-modules/src/utils/SystemSwitch.sol index 6836433d56..e68bd73844 100644 --- a/packages/world-modules/src/utils/SystemSwitch.sol +++ b/packages/world-modules/src/utils/SystemSwitch.sol @@ -11,6 +11,7 @@ import { AccessControl } from "@latticexyz/world/src/AccessControl.sol"; import { ROOT_NAMESPACE } from "@latticexyz/world/src/constants.sol"; import { revertWithBytes } from "@latticexyz/world/src/revertWithBytes.sol"; import { BEFORE_CALL_SYSTEM, AFTER_CALL_SYSTEM } from "@latticexyz/world/src/systemHookTypes.sol"; +import { SystemCall } from "@latticexyz/world/src/SystemCall.sol"; import { IWorldErrors } from "@latticexyz/world/src/IWorldErrors.sol"; import { ISystemHook } from "@latticexyz/world/src/ISystemHook.sol"; @@ -39,17 +40,13 @@ library SystemSwitch { function call(ResourceId systemId, bytes memory callData) internal returns (bytes memory returnData) { address worldAddress = WorldContextConsumerLib._world(); - // If we're in the World context, call the system directly via delegatecall + // If we're in the World context, call via the internal library if (address(this) == worldAddress) { - (address systemAddress, ) = Systems.get(systemId); - // Check if the system exists - if (systemAddress == address(0)) revert IWorldErrors.World_ResourceNotFound(systemId, systemId.toString()); - bool success; - (success, returnData) = WorldContextProviderLib.delegatecallWithContext({ - msgSender: WorldContextConsumerLib._msgSender(), - msgValue: WorldContextConsumerLib._msgValue(), - target: systemAddress, + (success, returnData) = SystemCall.call({ + caller: WorldContextConsumerLib._msgSender(), + value: 0, + systemId: systemId, callData: callData }); diff --git a/packages/world-modules/test/SystemSwitch.t.sol b/packages/world-modules/test/SystemSwitch.t.sol index 765233bd08..2944b3bf31 100644 --- a/packages/world-modules/test/SystemSwitch.t.sol +++ b/packages/world-modules/test/SystemSwitch.t.sol @@ -4,6 +4,11 @@ pragma solidity >=0.8.24; import { Test } from "forge-std/Test.sol"; import { GasReporter } from "@latticexyz/gas-report/src/GasReporter.sol"; +import { IStoreErrors } from "@latticexyz/store/src/IStoreErrors.sol"; +import { ResourceIds, ResourceIdsTableId } from "@latticexyz/store/src/codegen/tables/ResourceIds.sol"; +import { Schema } from "@latticexyz/store/src/Schema.sol"; +import { StoreCore } from "@latticexyz/store/src/StoreCore.sol"; + import { System } from "@latticexyz/world/src/System.sol"; import { IBaseWorld } from "@latticexyz/world/src/codegen/interfaces/IBaseWorld.sol"; import { World } from "@latticexyz/world/src/World.sol"; @@ -22,7 +27,11 @@ contract EchoSystem is System { return _world(); } - function echo(string memory message) public view returns (string memory) { + function readTable() public view returns (Schema) { + return StoreCore.getKeySchema(ResourceIdsTableId); + } + + function echo(string memory message) public pure returns (string memory) { return message; } @@ -128,6 +137,12 @@ contract SystemSwitchTest is Test, GasReporter { assertEq(abi.decode(returnData, (string)), "hello"); } + function testCallRootFromRootReadTable() public { + vm.prank(caller); + bytes memory returnData = _executeFromSystemA(rootSystemBId, abi.encodeCall(EchoSystem.readTable, ())); + assertEq(Schema.unwrap(abi.decode(returnData, (Schema))), Schema.unwrap(ResourceIds.getKeySchema())); + } + // - ROOT FROM NON ROOT ---------------------------------------------------------------------------- // function testCallRootFromNonRootMsgSender() public { @@ -161,6 +176,12 @@ contract SystemSwitchTest is Test, GasReporter { assertEq(abi.decode(returnData, (string)), "hello"); } + function testCallRootFromNonRootReadTable() public { + vm.prank(caller); + bytes memory returnData = _executeFromSystemA(rootSystemBId, abi.encodeCall(EchoSystem.readTable, ())); + assertEq(Schema.unwrap(abi.decode(returnData, (Schema))), Schema.unwrap(ResourceIds.getKeySchema())); + } + // - NON ROOT FROM ROOT ---------------------------------------------------------------------------- // function testCallNonRootFromRootMsgSender() public { @@ -169,19 +190,19 @@ contract SystemSwitchTest is Test, GasReporter { assertEq(abi.decode(returnData, (address)), caller); } - function testNonCallRootFromRootWorld() public { + function testCallNonRootFromRootWorld() public { vm.prank(caller); bytes memory returnData = _executeFromRootSystemA(systemBId, abi.encodeCall(EchoSystem.world, ())); assertEq(abi.decode(returnData, (address)), address(world)); } - function testNonCallRootFromRootEcho() public { + function testCallNonRootFromRootEcho() public { vm.prank(caller); bytes memory returnData = _executeFromRootSystemA(systemBId, abi.encodeCall(EchoSystem.echo, ("hello"))); assertEq(abi.decode(returnData, (string)), "hello"); } - function testNonCallRootFromRootWorldSelector() public { + function testCallNonRootFromRootWorldSelector() public { bytes4 worldFunctionSelector = world.registerRootFunctionSelector( systemBId, "echo(string)", @@ -194,6 +215,20 @@ contract SystemSwitchTest is Test, GasReporter { assertEq(abi.decode(returnData, (string)), "hello"); } + function testCallNonRootFromRootReadTable() public { + vm.prank(caller); + + // Call reverts because the non-root system storage does not have table schemas + vm.expectRevert( + abi.encodeWithSelector( + IStoreErrors.Store_TableNotFound.selector, + ResourceIdsTableId, + string(abi.encodePacked(ResourceIdsTableId)) + ) + ); + world.call(systemAId, abi.encodeCall(EchoSystem.call, (systemBId, abi.encodeCall(EchoSystem.readTable, ())))); + } + // - NON ROOT FROM NON ROOT ---------------------------------------------------------------------------- // function testCallNonRootFromNonRootMsgSender() public { @@ -202,19 +237,19 @@ contract SystemSwitchTest is Test, GasReporter { assertEq(abi.decode(returnData, (address)), address(systemA)); } - function testNonCallRootFromNonRootWorld() public { + function testCallNonRootFromNonRootWorld() public { vm.prank(caller); bytes memory returnData = _executeFromSystemA(systemBId, abi.encodeCall(EchoSystem.world, ())); assertEq(abi.decode(returnData, (address)), address(world)); } - function testNonCallRootFromNonRootEcho() public { + function testCallNonRootFromNonRootEcho() public { vm.prank(caller); bytes memory returnData = _executeFromSystemA(systemBId, abi.encodeCall(EchoSystem.echo, ("hello"))); assertEq(abi.decode(returnData, (string)), "hello"); } - function testNonCallRootFromNonRootWorldSelector() public { + function testCallNonRootFromNonRootWorldSelector() public { bytes4 worldFunctionSelector = world.registerRootFunctionSelector( systemBId, "echo(string)", @@ -226,4 +261,18 @@ contract SystemSwitchTest is Test, GasReporter { bytes memory returnData = _executeFromSystemA(callData); assertEq(abi.decode(returnData, (string)), "hello"); } + + function testCallNonRootFromNonRootReadTable() public { + vm.prank(caller); + + // Call reverts because the non-root system storage does not have table schemas + vm.expectRevert( + abi.encodeWithSelector( + IStoreErrors.Store_TableNotFound.selector, + ResourceIdsTableId, + string(abi.encodePacked(ResourceIdsTableId)) + ) + ); + world.call(systemAId, abi.encodeCall(EchoSystem.call, (systemBId, abi.encodeCall(EchoSystem.readTable, ())))); + } }