diff --git a/.changeset/olive-foxes-shop.md b/.changeset/olive-foxes-shop.md new file mode 100644 index 0000000000..5ad83f87fe --- /dev/null +++ b/.changeset/olive-foxes-shop.md @@ -0,0 +1,7 @@ +--- +"@latticexyz/world-modules": patch +"@latticexyz/world": major +--- + +We now expose a `WorldContextConsumerLib` library with the same functionality as the `WorldContextConsumer` contract, but the ability to be used inside of internal libraries. +We also renamed the `WorldContextProvider` library to `WorldContextProviderLib` for consistency. diff --git a/packages/world-modules/gas-report.json b/packages/world-modules/gas-report.json index dbe08a14b7..c806f19a87 100644 --- a/packages/world-modules/gas-report.json +++ b/packages/world-modules/gas-report.json @@ -3,13 +3,13 @@ "file": "test/KeysInTableModule.t.sol", "test": "testInstallComposite", "name": "install keys in table module", - "gasUsed": 1407523 + "gasUsed": 1407698 }, { "file": "test/KeysInTableModule.t.sol", "test": "testInstallGas", "name": "install keys in table module", - "gasUsed": 1407523 + "gasUsed": 1407698 }, { "file": "test/KeysInTableModule.t.sol", @@ -21,13 +21,13 @@ "file": "test/KeysInTableModule.t.sol", "test": "testInstallSingleton", "name": "install keys in table module", - "gasUsed": 1407523 + "gasUsed": 1407698 }, { "file": "test/KeysInTableModule.t.sol", "test": "testSetAndDeleteRecordHookCompositeGas", "name": "install keys in table module", - "gasUsed": 1407523 + "gasUsed": 1407698 }, { "file": "test/KeysInTableModule.t.sol", @@ -45,7 +45,7 @@ "file": "test/KeysInTableModule.t.sol", "test": "testSetAndDeleteRecordHookGas", "name": "install keys in table module", - "gasUsed": 1407523 + "gasUsed": 1407698 }, { "file": "test/KeysInTableModule.t.sol", @@ -63,7 +63,7 @@ "file": "test/KeysWithValueModule.t.sol", "test": "testGetKeysWithValueGas", "name": "install keys with value module", - "gasUsed": 648854 + "gasUsed": 648994 }, { "file": "test/KeysWithValueModule.t.sol", @@ -81,7 +81,7 @@ "file": "test/KeysWithValueModule.t.sol", "test": "testInstall", "name": "install keys with value module", - "gasUsed": 648854 + "gasUsed": 648994 }, { "file": "test/KeysWithValueModule.t.sol", @@ -93,7 +93,7 @@ "file": "test/KeysWithValueModule.t.sol", "test": "testSetAndDeleteRecordHook", "name": "install keys with value module", - "gasUsed": 648854 + "gasUsed": 648994 }, { "file": "test/KeysWithValueModule.t.sol", @@ -111,7 +111,7 @@ "file": "test/KeysWithValueModule.t.sol", "test": "testSetField", "name": "install keys with value module", - "gasUsed": 648854 + "gasUsed": 648994 }, { "file": "test/KeysWithValueModule.t.sol", @@ -195,31 +195,31 @@ "file": "test/StandardDelegationsModule.t.sol", "test": "testCallFromCallboundDelegation", "name": "register a callbound delegation", - "gasUsed": 117315 + "gasUsed": 117420 }, { "file": "test/StandardDelegationsModule.t.sol", "test": "testCallFromCallboundDelegation", "name": "call a system via a callbound delegation", - "gasUsed": 36583 + "gasUsed": 36688 }, { "file": "test/StandardDelegationsModule.t.sol", "test": "testCallFromTimeboundDelegation", "name": "register a timebound delegation", - "gasUsed": 111809 + "gasUsed": 111914 }, { "file": "test/StandardDelegationsModule.t.sol", "test": "testCallFromTimeboundDelegation", "name": "call a system via a timebound delegation", - "gasUsed": 26745 + "gasUsed": 26815 }, { "file": "test/UniqueEntityModule.t.sol", "test": "testInstall", "name": "install unique entity module", - "gasUsed": 675100 + "gasUsed": 675275 }, { "file": "test/UniqueEntityModule.t.sol", @@ -231,7 +231,7 @@ "file": "test/UniqueEntityModule.t.sol", "test": "testInstallRoot", "name": "installRoot unique entity module", - "gasUsed": 642496 + "gasUsed": 642601 }, { "file": "test/UniqueEntityModule.t.sol", diff --git a/packages/world/gas-report.json b/packages/world/gas-report.json index 148fd99d39..36848f6dbc 100644 --- a/packages/world/gas-report.json +++ b/packages/world/gas-report.json @@ -45,37 +45,37 @@ "file": "test/BatchCall.t.sol", "test": "testBatchCallFromReturnData", "name": "call systems with batchCallFrom", - "gasUsed": 46377 + "gasUsed": 46444 }, { "file": "test/BatchCall.t.sol", "test": "testBatchCallReturnData", "name": "call systems with batchCall", - "gasUsed": 45171 + "gasUsed": 45206 }, { "file": "test/World.t.sol", "test": "testCall", "name": "call a system via the World", - "gasUsed": 12335 + "gasUsed": 12370 }, { "file": "test/World.t.sol", "test": "testCallFromNamespaceDelegation", "name": "call a system via a namespace fallback delegation", - "gasUsed": 26073 + "gasUsed": 26143 }, { "file": "test/World.t.sol", "test": "testCallFromUnlimitedDelegation", "name": "register an unlimited delegation", - "gasUsed": 47588 + "gasUsed": 47623 }, { "file": "test/World.t.sol", "test": "testCallFromUnlimitedDelegation", "name": "call a system via an unlimited delegation", - "gasUsed": 12771 + "gasUsed": 12806 }, { "file": "test/World.t.sol", @@ -93,31 +93,31 @@ "file": "test/World.t.sol", "test": "testRegisterFunctionSelector", "name": "Register a function selector", - "gasUsed": 83114 + "gasUsed": 83149 }, { "file": "test/World.t.sol", "test": "testRegisterNamespace", "name": "Register a new namespace", - "gasUsed": 120817 + "gasUsed": 120887 }, { "file": "test/World.t.sol", "test": "testRegisterRootFunctionSelector", "name": "Register a root function selector", - "gasUsed": 80398 + "gasUsed": 80433 }, { "file": "test/World.t.sol", "test": "testRegisterSystem", "name": "register a system", - "gasUsed": 162559 + "gasUsed": 162594 }, { "file": "test/World.t.sol", "test": "testRegisterTable", "name": "Register a new table in the namespace", - "gasUsed": 637329 + "gasUsed": 637399 }, { "file": "test/World.t.sol", diff --git a/packages/world/src/SystemCall.sol b/packages/world/src/SystemCall.sol index 3efc83a8a4..dc44eaf3a4 100644 --- a/packages/world/src/SystemCall.sol +++ b/packages/world/src/SystemCall.sol @@ -4,10 +4,10 @@ pragma solidity >=0.8.21; import { Hook } from "@latticexyz/store/src/Hook.sol"; import { ResourceId, WorldResourceIdInstance } from "./WorldResourceId.sol"; -import { WorldContextProvider } from "./WorldContext.sol"; +import { WorldContextProviderLib } from "./WorldContext.sol"; import { AccessControl } from "./AccessControl.sol"; import { ROOT_NAMESPACE } from "./constants.sol"; -import { WorldContextProvider } from "./WorldContext.sol"; +import { WorldContextProviderLib } from "./WorldContext.sol"; import { revertWithBytes } from "./revertWithBytes.sol"; import { BEFORE_CALL_SYSTEM, AFTER_CALL_SYSTEM } from "./systemHookTypes.sol"; @@ -61,13 +61,13 @@ library SystemCall { // Call the system and forward any return data (success, data) = systemId.getNamespace() == ROOT_NAMESPACE // Use delegatecall for root systems (= registered in the root namespace) - ? WorldContextProvider.delegatecallWithContext({ + ? WorldContextProviderLib.delegatecallWithContext({ msgSender: caller, msgValue: value, target: systemAddress, callData: callData }) - : WorldContextProvider.callWithContext({ + : WorldContextProviderLib.callWithContext({ msgSender: caller, msgValue: value, target: systemAddress, diff --git a/packages/world/src/World.sol b/packages/world/src/World.sol index 030adc466b..5841ce098e 100644 --- a/packages/world/src/World.sol +++ b/packages/world/src/World.sol @@ -15,7 +15,7 @@ import { ResourceId, WorldResourceIdInstance } from "./WorldResourceId.sol"; import { ROOT_NAMESPACE_ID, ROOT_NAMESPACE, ROOT_NAME } from "./constants.sol"; import { AccessControl } from "./AccessControl.sol"; import { SystemCall } from "./SystemCall.sol"; -import { WorldContextProvider } from "./WorldContext.sol"; +import { WorldContextProviderLib } from "./WorldContext.sol"; import { revertWithBytes } from "./revertWithBytes.sol"; import { Delegation } from "./Delegation.sol"; import { requireInterface } from "./requireInterface.sol"; @@ -107,7 +107,7 @@ contract World is StoreData, IWorldKernel { // Require the provided address to implement the IModule interface requireInterface(address(module), MODULE_INTERFACE_ID); - WorldContextProvider.delegatecallWithContextOrRevert({ + WorldContextProviderLib.delegatecallWithContextOrRevert({ msgSender: msg.sender, msgValue: 0, target: address(module), diff --git a/packages/world/src/WorldContext.sol b/packages/world/src/WorldContext.sol index f5e846e05f..d5e6f3a723 100644 --- a/packages/world/src/WorldContext.sol +++ b/packages/world/src/WorldContext.sol @@ -18,17 +18,12 @@ uint256 constant CONTEXT_BYTES = 20 + 32; */ abstract contract WorldContextConsumer is IWorldContextConsumer { /** - * @notice Extracts the trusted msg.sender value from the appended calldata. - * @return sender The address of the trusted sender. + * @notice Extract the `msg.sender` from the context appended to the calldata. + * @return sender The `msg.sender` in the call to the World contract before the World routed the + * call to the WorldContextConsumer contract. */ function _msgSender() public view returns (address sender) { - assembly { - // Load 32 bytes from calldata at position calldatasize() - context size, - // then shift left 96 bits (to right-align the address) - // 96 = 256 - 20 * 8 - sender := shr(96, calldataload(sub(calldatasize(), CONTEXT_BYTES))) - } - if (sender == address(0)) sender = msg.sender; + return WorldContextConsumerLib._msgSender(); } /** @@ -37,10 +32,7 @@ abstract contract WorldContextConsumer is IWorldContextConsumer { * call to the WorldContextConsumer contract. */ function _msgValue() public pure returns (uint256 value) { - assembly { - // Load 32 bytes from calldata at position calldatasize() - 32 bytes, - value := calldataload(sub(calldatasize(), 32)) - } + return WorldContextConsumerLib._msgValue(); } /** @@ -62,12 +54,49 @@ abstract contract WorldContextConsumer is IWorldContextConsumer { } } +library WorldContextConsumerLib { + /** + * @notice Extract the `msg.sender` from the context appended to the calldata. + * @return sender The `msg.sender` in the call to the World contract before the World routed the + * call to the WorldContextConsumer contract. + */ + function _msgSender() internal view returns (address sender) { + assembly { + // Load 32 bytes from calldata at position calldatasize() - context size, + // then shift left 96 bits (to right-align the address) + // 96 = 256 - 20 * 8 + sender := shr(96, calldataload(sub(calldatasize(), CONTEXT_BYTES))) + } + if (sender == address(0)) sender = msg.sender; + } + + /** + * @notice Extract the `msg.value` from the context appended to the calldata. + * @return value The `msg.value` in the call to the World contract before the World routed the + * call to the WorldContextConsumer contract. + */ + function _msgValue() internal pure returns (uint256 value) { + assembly { + // Load 32 bytes from calldata at position calldatasize() - 32 bytes, + value := calldataload(sub(calldatasize(), 32)) + } + } + + /** + * @notice Get the address of the World contract that routed the call to this WorldContextConsumer. + * @return The address of the World contract that routed the call to this WorldContextConsumer. + */ + function _world() internal view returns (address) { + return StoreSwitch.getStoreAddress(); + } +} + /** - * @title WorldContextProvider - Utility functions to call contracts with context values appended to calldata. + * @title WorldContextProviderLib - Utility functions to call contracts with context values appended to calldata. * @notice This library provides functions to make calls or delegatecalls to other contracts, * appending the context values (like msg.sender and msg.value) to the calldata for WorldContextConsumer to consume. */ -library WorldContextProvider { +library WorldContextProviderLib { /** * @notice Appends context values to the given calldata. * @param callData The original calldata. diff --git a/packages/world/src/modules/core/CoreModule.sol b/packages/world/src/modules/core/CoreModule.sol index 21b27ed620..a952dc559d 100644 --- a/packages/world/src/modules/core/CoreModule.sol +++ b/packages/world/src/modules/core/CoreModule.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.21; -import { WorldContextProvider } from "../../WorldContext.sol"; +import { WorldContextProviderLib } from "../../WorldContext.sol"; import { ROOT_NAMESPACE, ROOT_NAMESPACE_ID, WORLD_NAMESPACE_ID } from "../../constants.sol"; import { Module } from "../../Module.sol"; @@ -104,7 +104,7 @@ contract CoreModule is Module { * @dev Uses the CoreSystem's `registerSystem` implementation to register itself on the World. */ function _registerCoreSystem() internal { - WorldContextProvider.delegatecallWithContextOrRevert({ + WorldContextProviderLib.delegatecallWithContextOrRevert({ msgSender: _msgSender(), msgValue: 0, target: coreSystem, @@ -148,7 +148,7 @@ contract CoreModule is Module { for (uint256 i = 0; i < functionSignatures.length; i++) { // Use the CoreSystem's `registerRootFunctionSelector` to register the // root function selectors in the World. - WorldContextProvider.delegatecallWithContextOrRevert({ + WorldContextProviderLib.delegatecallWithContextOrRevert({ msgSender: _msgSender(), msgValue: 0, target: coreSystem, diff --git a/packages/world/src/modules/core/implementations/ModuleInstallationSystem.sol b/packages/world/src/modules/core/implementations/ModuleInstallationSystem.sol index af77bc3759..4fd4a1c182 100644 --- a/packages/world/src/modules/core/implementations/ModuleInstallationSystem.sol +++ b/packages/world/src/modules/core/implementations/ModuleInstallationSystem.sol @@ -4,7 +4,7 @@ pragma solidity >=0.8.21; import { IModule, MODULE_INTERFACE_ID } from "../../../IModule.sol"; import { System } from "../../../System.sol"; import { AccessControl } from "../../../AccessControl.sol"; -import { WorldContextProvider } from "../../../WorldContext.sol"; +import { WorldContextProviderLib } from "../../../WorldContext.sol"; import { ResourceAccess } from "../../../codegen/tables/ResourceAccess.sol"; import { InstalledModules } from "../../../codegen/tables/InstalledModules.sol"; import { requireInterface } from "../../../requireInterface.sol"; @@ -25,7 +25,7 @@ contract ModuleInstallationSystem is System { // Require the provided address to implement the IModule interface requireInterface(address(module), MODULE_INTERFACE_ID); - WorldContextProvider.callWithContextOrRevert({ + WorldContextProviderLib.callWithContextOrRevert({ msgSender: _msgSender(), msgValue: 0, target: address(module), diff --git a/packages/world/src/modules/core/implementations/StoreRegistrationSystem.sol b/packages/world/src/modules/core/implementations/StoreRegistrationSystem.sol index 9590bdd45e..96baa03cd6 100644 --- a/packages/world/src/modules/core/implementations/StoreRegistrationSystem.sol +++ b/packages/world/src/modules/core/implementations/StoreRegistrationSystem.sol @@ -13,7 +13,7 @@ import { ROOT_NAMESPACE, ROOT_NAME } from "../../../constants.sol"; import { AccessControl } from "../../../AccessControl.sol"; import { requireInterface } from "../../../requireInterface.sol"; import { revertWithBytes } from "../../../revertWithBytes.sol"; -import { WorldContextProvider } from "../../../WorldContext.sol"; +import { WorldContextProviderLib } from "../../../WorldContext.sol"; import { NamespaceOwner } from "../../../codegen/tables/NamespaceOwner.sol"; import { ResourceAccess } from "../../../codegen/tables/ResourceAccess.sol"; diff --git a/packages/world/test/World.t.sol b/packages/world/test/World.t.sol index eb22836447..730e8ab6dd 100644 --- a/packages/world/test/World.t.sol +++ b/packages/world/test/World.t.sol @@ -27,7 +27,7 @@ import { System } from "../src/System.sol"; import { ResourceId, WorldResourceIdLib, WorldResourceIdInstance } from "../src/WorldResourceId.sol"; import { ROOT_NAMESPACE, ROOT_NAME, ROOT_NAMESPACE_ID, UNLIMITED_DELEGATION } from "../src/constants.sol"; import { RESOURCE_TABLE, RESOURCE_SYSTEM, RESOURCE_NAMESPACE } from "../src/worldResourceTypes.sol"; -import { WorldContextProvider, WORLD_CONTEXT_CONSUMER_INTERFACE_ID } from "../src/WorldContext.sol"; +import { WorldContextProviderLib, WORLD_CONTEXT_CONSUMER_INTERFACE_ID } from "../src/WorldContext.sol"; import { SystemHook } from "../src/SystemHook.sol"; import { BEFORE_CALL_SYSTEM, AFTER_CALL_SYSTEM } from "../src/systemHookTypes.sol"; import { Module, MODULE_INTERFACE_ID } from "../src/Module.sol"; @@ -900,7 +900,7 @@ contract WorldTest is Test, GasReporter { WorldTestSystem.delegateCallSubSystem, // Function in system ( address(subSystem), // Address of subsystem - WorldContextProvider.appendContext({ + WorldContextProviderLib.appendContext({ callData: abi.encodeCall(WorldTestSystem.msgSender, ()), msgSender: address(this), msgValue: uint256(0) diff --git a/packages/world/test/WorldContext.t.sol b/packages/world/test/WorldContext.t.sol index 5015e0968f..e0f74baa83 100644 --- a/packages/world/test/WorldContext.t.sol +++ b/packages/world/test/WorldContext.t.sol @@ -3,7 +3,7 @@ pragma solidity >=0.8.21; import "forge-std/Test.sol"; import { GasReporter } from "@latticexyz/gas-report/src/GasReporter.sol"; -import { WorldContextProvider, WorldContextConsumer } from "../src/WorldContext.sol"; +import { WorldContextProviderLib, WorldContextConsumer } from "../src/WorldContext.sol"; contract TestContextConsumer is WorldContextConsumer { event Context(bytes args, address msgSender, uint256 msgValue); @@ -21,7 +21,7 @@ contract WorldContextTest is Test, GasReporter { function testFuzzAppendContext(bytes memory callData, address msgSender, uint256 msgValue) public { assertEq( keccak256(abi.encodePacked(callData, msgSender, msgValue)), - keccak256(WorldContextProvider.appendContext(callData, msgSender, msgValue)) + keccak256(WorldContextProviderLib.appendContext(callData, msgSender, msgValue)) ); } @@ -30,7 +30,7 @@ contract WorldContextTest is Test, GasReporter { vm.expectEmit(true, true, true, true); emit Context(args, msgSender, msgValue); - WorldContextProvider.callWithContextOrRevert({ + WorldContextProviderLib.callWithContextOrRevert({ msgSender: msgSender, msgValue: msgValue, target: address(consumer), @@ -43,7 +43,7 @@ contract WorldContextTest is Test, GasReporter { vm.expectEmit(true, true, true, true); emit Context(args, msgSender, msgValue); - WorldContextProvider.delegatecallWithContextOrRevert({ + WorldContextProviderLib.delegatecallWithContextOrRevert({ msgSender: msgSender, msgValue: msgValue, target: address(consumer),