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

fix(world-modules): SystemSwitch properly calls systems from root #2205

Merged
merged 15 commits into from
Feb 7, 2024
5 changes: 5 additions & 0 deletions .changeset/light-carrots-applaud.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@latticexyz/world-modules": minor
---

Fixed `SystemSwitch` to properly call non-root systems from root systems.
15 changes: 6 additions & 9 deletions packages/world-modules/src/utils/SystemSwitch.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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
});

Expand Down
63 changes: 56 additions & 7 deletions packages/world-modules/test/SystemSwitch.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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;
}

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand All @@ -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)",
Expand All @@ -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 {
Expand All @@ -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)",
Expand All @@ -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, ()))));
}
}
Loading