From f146cfe9234f0aa66460c3f771c71adb40f42341 Mon Sep 17 00:00:00 2001 From: alvrs Date: Wed, 18 Oct 2023 21:51:19 +0100 Subject: [PATCH 01/13] feat(world-modules): add puppet module --- packages/world-modules/mud.config.ts | 33 ++- .../src/modules/puppet/Puppet.sol | 80 ++++++ .../puppet/PuppetDelegationControl.sol | 27 +++ .../src/modules/puppet/PuppetMaster.sol | 18 ++ .../src/modules/puppet/PuppetModule.sol | 47 ++++ .../src/modules/puppet/constants.sol | 18 ++ .../src/modules/puppet/registerPuppet.sol | 17 ++ .../modules/puppet/tables/PuppetRegistry.sol | 229 ++++++++++++++++++ .../src/utils/AccessControlLib.sol | 54 +++++ .../world-modules/test/PuppetModule.t.sol | 73 ++++++ 10 files changed, 595 insertions(+), 1 deletion(-) create mode 100644 packages/world-modules/src/modules/puppet/Puppet.sol create mode 100644 packages/world-modules/src/modules/puppet/PuppetDelegationControl.sol create mode 100644 packages/world-modules/src/modules/puppet/PuppetMaster.sol create mode 100644 packages/world-modules/src/modules/puppet/PuppetModule.sol create mode 100644 packages/world-modules/src/modules/puppet/constants.sol create mode 100644 packages/world-modules/src/modules/puppet/registerPuppet.sol create mode 100644 packages/world-modules/src/modules/puppet/tables/PuppetRegistry.sol create mode 100644 packages/world-modules/src/utils/AccessControlLib.sol create mode 100644 packages/world-modules/test/PuppetModule.t.sol diff --git a/packages/world-modules/mud.config.ts b/packages/world-modules/mud.config.ts index bf8e4bc6e7..dfff9fbda9 100644 --- a/packages/world-modules/mud.config.ts +++ b/packages/world-modules/mud.config.ts @@ -10,7 +10,7 @@ export default mudConfig({ tables: { /************************************************************************ * - * MODULE TABLES + * KEYS WITH VALUE MODULE * ************************************************************************/ KeysWithValue: { @@ -24,6 +24,11 @@ export default mudConfig({ tableIdArgument: true, storeArgument: true, }, + /************************************************************************ + * + * KEYS IN TABLE MODULE + * + ************************************************************************/ KeysInTable: { directory: "modules/keysintable/tables", keySchema: { sourceTableId: "ResourceId" }, @@ -46,6 +51,11 @@ export default mudConfig({ dataStruct: false, storeArgument: true, }, + /************************************************************************ + * + * UNIQUE ENTITY MODULE + * + ************************************************************************/ UniqueEntity: { directory: "modules/uniqueentity/tables", keySchema: {}, @@ -53,6 +63,11 @@ export default mudConfig({ tableIdArgument: true, storeArgument: true, }, + /************************************************************************ + * + * STD DELEGATIONS MODULE + * + ************************************************************************/ CallboundDelegations: { directory: "modules/std-delegations/tables", keySchema: { @@ -75,6 +90,22 @@ export default mudConfig({ maxTimestamp: "uint256", }, }, + /************************************************************************ + * + * PUPPET MODULE + * + ************************************************************************/ + PuppetRegistry: { + directory: "modules/puppet/tables", + keySchema: { + systemId: "ResourceId", + }, + valueSchema: { + puppet: "address", + }, + tableIdArgument: true, + }, }, + excludeSystems: ["UniqueEntitySystem"], }); diff --git a/packages/world-modules/src/modules/puppet/Puppet.sol b/packages/world-modules/src/modules/puppet/Puppet.sol new file mode 100644 index 0000000000..0b688b2ca0 --- /dev/null +++ b/packages/world-modules/src/modules/puppet/Puppet.sol @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.21; + +import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol"; +import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; +import { IBaseWorld } from "@latticexyz/world/src/codegen/interfaces/IBaseWorld.sol"; +import { Systems } from "@latticexyz/world/src/codegen/tables/Systems.sol"; + +contract Puppet { + error Puppet_AccessDenied(address caller); + + IBaseWorld public immutable world; + ResourceId public immutable systemId; + + constructor(IBaseWorld _world, ResourceId _systemId) { + world = _world; + systemId = _systemId; + StoreSwitch.setStoreAddress(address(_world)); + } + + modifier onlyPuppetMaster() { + (address systemAddress, ) = Systems.get(systemId); + if (msg.sender != systemAddress) { + revert Puppet_AccessDenied(msg.sender); + } + _; + } + + fallback() external { + // Forward all calls to the system in the world + bytes memory returnData = world.callFrom(msg.sender, systemId, msg.data); + + // If the call was successful, return the return data + assembly { + return(add(returnData, 0x20), mload(returnData)) + } + } + + /** + * @dev Log an event with a signature and no additional topic + */ + function log(bytes32 eventSignature, bytes memory eventData) public onlyPuppetMaster { + assembly { + log1(add(eventData, 0x20), mload(eventData), eventSignature) + } + } + + /** + * @dev Log an event with a signature and one additional topics + */ + function log(bytes32 eventSignature, bytes32 topic1, bytes memory eventData) public onlyPuppetMaster { + assembly { + log2(add(eventData, 0x20), mload(eventData), eventSignature, topic1) + } + } + + /** + * @dev Log an event with a signature and two additional topics + */ + function log(bytes32 eventSignature, bytes32 topic1, bytes32 topic2, bytes memory eventData) public onlyPuppetMaster { + assembly { + log3(add(eventData, 0x20), mload(eventData), eventSignature, topic1, topic2) + } + } + + /** + * @dev Log an event with a signature and three additional topics + */ + function log( + bytes32 eventSignature, + bytes32 topic1, + bytes32 topic2, + bytes32 topic3, + bytes memory eventData + ) public onlyPuppetMaster { + assembly { + log4(add(eventData, 0x20), mload(eventData), eventSignature, topic1, topic2, topic3) + } + } +} diff --git a/packages/world-modules/src/modules/puppet/PuppetDelegationControl.sol b/packages/world-modules/src/modules/puppet/PuppetDelegationControl.sol new file mode 100644 index 0000000000..bf67a50792 --- /dev/null +++ b/packages/world-modules/src/modules/puppet/PuppetDelegationControl.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.21; + +import { DelegationControl } from "@latticexyz/world/src/DelegationControl.sol"; +import { ResourceId } from "@latticexyz/world/src/WorldResourceId.sol"; +import { AccessControlLib } from "../../utils/AccessControlLib.sol"; +import { PuppetRegistry } from "./tables/PuppetRegistry.sol"; +import { PUPPET_TABLE_ID } from "./constants.sol"; + +contract PuppetDelegationControl is DelegationControl { + /** + * Verify a delegation by checking if the resourceId maps to the caller as puppet + */ + function verify(address, ResourceId systemId, bytes memory) public view returns (bool) { + address puppet = _msgSender(); + return PuppetRegistry.get(PUPPET_TABLE_ID, systemId) == puppet; + } + + /** + * Initialize a delegation by storing the mapping from systemId to puppet + */ + function initDelegation(ResourceId systemId, address puppet) public { + // Require the caller to be the owner of the system + AccessControlLib.requireOwner(systemId, _msgSender()); + PuppetRegistry.set(PUPPET_TABLE_ID, systemId, puppet); + } +} diff --git a/packages/world-modules/src/modules/puppet/PuppetMaster.sol b/packages/world-modules/src/modules/puppet/PuppetMaster.sol new file mode 100644 index 0000000000..4659337772 --- /dev/null +++ b/packages/world-modules/src/modules/puppet/PuppetMaster.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.21; +import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; +import { SystemRegistry } from "@latticexyz/world/src/codegen/tables/SystemRegistry.sol"; +import { PuppetRegistry } from "./tables/PuppetRegistry.sol"; +import { PUPPET_TABLE_ID } from "./constants.sol"; +import { Puppet } from "./Puppet.sol"; + +contract PuppetMaster { + error PuppetMaster_NoPuppet(address systemAddress, ResourceId systemId); + + function puppet() internal returns (Puppet) { + ResourceId systemId = SystemRegistry.getSystemId(address(this)); + address puppetAddress = PuppetRegistry.get(PUPPET_TABLE_ID, systemId); + if (puppetAddress == address(0)) revert PuppetMaster_NoPuppet(address(this), systemId); + return Puppet(puppetAddress); + } +} diff --git a/packages/world-modules/src/modules/puppet/PuppetModule.sol b/packages/world-modules/src/modules/puppet/PuppetModule.sol new file mode 100644 index 0000000000..cee2e6a8b4 --- /dev/null +++ b/packages/world-modules/src/modules/puppet/PuppetModule.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.21; + +import { IBaseWorld } from "@latticexyz/world/src/codegen/interfaces/IBaseWorld.sol"; + +import { Module } from "@latticexyz/world/src/Module.sol"; +import { WorldContextConsumer } from "@latticexyz/world/src/WorldContext.sol"; +import { revertWithBytes } from "@latticexyz/world/src/revertWithBytes.sol"; + +import { PuppetDelegationControl } from "./PuppetDelegationControl.sol"; +import { MODULE_NAME, PUPPET_DELEGATION, PUPPET_TABLE_ID } from "./constants.sol"; + +import { PuppetRegistry } from "./tables/PuppetRegistry.sol"; + +/** + * This module registers tables and delegation control systems required for puppet delegations + */ +contract PuppetModule is Module { + PuppetDelegationControl private immutable puppetDelegationControl = new PuppetDelegationControl(); + + function getName() public pure returns (bytes16) { + return MODULE_NAME; + } + + function installRoot(bytes memory) public { + IBaseWorld world = IBaseWorld(_world()); + + // Register table + PuppetRegistry.register(PUPPET_TABLE_ID); + + // Register system + (bool success, bytes memory returnData) = address(world).delegatecall( + abi.encodeCall(world.registerSystem, (PUPPET_DELEGATION, puppetDelegationControl, true)) + ); + if (!success) revertWithBytes(returnData); + } + + function install(bytes memory) public { + IBaseWorld world = IBaseWorld(_world()); + + // Register table + PuppetRegistry.register(PUPPET_TABLE_ID); + + // Register system + world.registerSystem(PUPPET_DELEGATION, puppetDelegationControl, true); + } +} diff --git a/packages/world-modules/src/modules/puppet/constants.sol b/packages/world-modules/src/modules/puppet/constants.sol new file mode 100644 index 0000000000..ac6b6fbe40 --- /dev/null +++ b/packages/world-modules/src/modules/puppet/constants.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.21; + +import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; +import { RESOURCE_TABLE } from "@latticexyz/store/src/storeResourceTypes.sol"; +import { RESOURCE_SYSTEM } from "@latticexyz/world/src/worldResourceTypes.sol"; +import { ROOT_NAMESPACE } from "@latticexyz/world/src/constants.sol"; + +bytes16 constant MODULE_NAME = bytes16("puppet"); +bytes14 constant NAMESPACE = bytes14("puppet"); + +ResourceId constant PUPPET_DELEGATION = ResourceId.wrap( + bytes32(abi.encodePacked(RESOURCE_SYSTEM, NAMESPACE, bytes16("Delegation"))) +); + +ResourceId constant PUPPET_TABLE_ID = ResourceId.wrap( + bytes32(abi.encodePacked(RESOURCE_TABLE, NAMESPACE, bytes16("PuppetRegistry"))) +); diff --git a/packages/world-modules/src/modules/puppet/registerPuppet.sol b/packages/world-modules/src/modules/puppet/registerPuppet.sol new file mode 100644 index 0000000000..dd097c7c1b --- /dev/null +++ b/packages/world-modules/src/modules/puppet/registerPuppet.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.21; +import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; +import { IBaseWorld } from "@latticexyz/world/src/codegen/interfaces/IBaseWorld.sol"; +import { WorldResourceIdInstance } from "@latticexyz/world/src/WorldResourceId.sol"; +import { PUPPET_DELEGATION } from "./constants.sol"; +import { PuppetDelegationControl } from "./PuppetDelegationControl.sol"; + +using WorldResourceIdInstance for ResourceId; + +function registerPuppet(IBaseWorld world, ResourceId systemId, address puppet) { + world.registerNamespaceDelegation( + systemId.getNamespaceId(), + PUPPET_DELEGATION, + abi.encodeCall(PuppetDelegationControl.initDelegation, (systemId, address(puppet))) + ); +} diff --git a/packages/world-modules/src/modules/puppet/tables/PuppetRegistry.sol b/packages/world-modules/src/modules/puppet/tables/PuppetRegistry.sol new file mode 100644 index 0000000000..054b093cbd --- /dev/null +++ b/packages/world-modules/src/modules/puppet/tables/PuppetRegistry.sol @@ -0,0 +1,229 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.21; + +/* 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"; + +// Import user types +import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; + +FieldLayout constant _fieldLayout = FieldLayout.wrap( + 0x0014010014000000000000000000000000000000000000000000000000000000 +); + +library PuppetRegistry { + /** + * @notice Get the table values' field layout. + * @return _fieldLayout The field layout for the table. + */ + function getFieldLayout() internal pure returns (FieldLayout) { + return _fieldLayout; + } + + /** + * @notice Get the table's key schema. + * @return _keySchema The key schema for the table. + */ + function getKeySchema() internal pure returns (Schema) { + SchemaType[] memory _keySchema = new SchemaType[](1); + _keySchema[0] = SchemaType.BYTES32; + + return SchemaLib.encode(_keySchema); + } + + /** + * @notice Get the table's value schema. + * @return _valueSchema The value schema for the table. + */ + function getValueSchema() internal pure returns (Schema) { + SchemaType[] memory _valueSchema = new SchemaType[](1); + _valueSchema[0] = SchemaType.ADDRESS; + + return SchemaLib.encode(_valueSchema); + } + + /** + * @notice Get the table's key field names. + * @return keyNames An array of strings with the names of key fields. + */ + function getKeyNames() internal pure returns (string[] memory keyNames) { + keyNames = new string[](1); + keyNames[0] = "systemId"; + } + + /** + * @notice Get the table's value field names. + * @return fieldNames An array of strings with the names of value fields. + */ + function getFieldNames() internal pure returns (string[] memory fieldNames) { + fieldNames = new string[](1); + fieldNames[0] = "puppet"; + } + + /** + * @notice Register the table with its config. + */ + function register(ResourceId _tableId) internal { + StoreSwitch.registerTable(_tableId, _fieldLayout, getKeySchema(), getValueSchema(), getKeyNames(), getFieldNames()); + } + + /** + * @notice Register the table with its config. + */ + function _register(ResourceId _tableId) internal { + StoreCore.registerTable(_tableId, _fieldLayout, getKeySchema(), getValueSchema(), getKeyNames(), getFieldNames()); + } + + /** + * @notice Get puppet. + */ + function getPuppet(ResourceId _tableId, ResourceId systemId) internal view returns (address puppet) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = ResourceId.unwrap(systemId); + + bytes32 _blob = StoreSwitch.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (address(bytes20(_blob))); + } + + /** + * @notice Get puppet. + */ + function _getPuppet(ResourceId _tableId, ResourceId systemId) internal view returns (address puppet) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = ResourceId.unwrap(systemId); + + bytes32 _blob = StoreCore.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (address(bytes20(_blob))); + } + + /** + * @notice Get puppet. + */ + function get(ResourceId _tableId, ResourceId systemId) internal view returns (address puppet) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = ResourceId.unwrap(systemId); + + bytes32 _blob = StoreSwitch.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (address(bytes20(_blob))); + } + + /** + * @notice Get puppet. + */ + function _get(ResourceId _tableId, ResourceId systemId) internal view returns (address puppet) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = ResourceId.unwrap(systemId); + + bytes32 _blob = StoreCore.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (address(bytes20(_blob))); + } + + /** + * @notice Set puppet. + */ + function setPuppet(ResourceId _tableId, ResourceId systemId, address puppet) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = ResourceId.unwrap(systemId); + + StoreSwitch.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((puppet)), _fieldLayout); + } + + /** + * @notice Set puppet. + */ + function _setPuppet(ResourceId _tableId, ResourceId systemId, address puppet) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = ResourceId.unwrap(systemId); + + StoreCore.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((puppet)), _fieldLayout); + } + + /** + * @notice Set puppet. + */ + function set(ResourceId _tableId, ResourceId systemId, address puppet) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = ResourceId.unwrap(systemId); + + StoreSwitch.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((puppet)), _fieldLayout); + } + + /** + * @notice Set puppet. + */ + function _set(ResourceId _tableId, ResourceId systemId, address puppet) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = ResourceId.unwrap(systemId); + + StoreCore.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((puppet)), _fieldLayout); + } + + /** + * @notice Delete all data for given keys. + */ + function deleteRecord(ResourceId _tableId, ResourceId systemId) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = ResourceId.unwrap(systemId); + + StoreSwitch.deleteRecord(_tableId, _keyTuple); + } + + /** + * @notice Delete all data for given keys. + */ + function _deleteRecord(ResourceId _tableId, ResourceId systemId) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = ResourceId.unwrap(systemId); + + StoreCore.deleteRecord(_tableId, _keyTuple, _fieldLayout); + } + + /** + * @notice Tightly pack static (fixed length) data using this table's schema. + * @return The static data, encoded into a sequence of bytes. + */ + function encodeStatic(address puppet) internal pure returns (bytes memory) { + return abi.encodePacked(puppet); + } + + /** + * @notice Encode all of a record's fields. + * @return The static (fixed length) data, encoded into a sequence of bytes. + * @return The lengths of the dynamic fields (packed into a single bytes32 value). + * @return The dyanmic (variable length) data, encoded into a sequence of bytes. + */ + function encode(address puppet) internal pure returns (bytes memory, PackedCounter, bytes memory) { + bytes memory _staticData = encodeStatic(puppet); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; + + return (_staticData, _encodedLengths, _dynamicData); + } + + /** + * @notice Encode keys as a bytes32 array using this table's field layout. + */ + function encodeKeyTuple(ResourceId systemId) internal pure returns (bytes32[] memory) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = ResourceId.unwrap(systemId); + + return _keyTuple; + } +} diff --git a/packages/world-modules/src/utils/AccessControlLib.sol b/packages/world-modules/src/utils/AccessControlLib.sol new file mode 100644 index 0000000000..462d683d67 --- /dev/null +++ b/packages/world-modules/src/utils/AccessControlLib.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.21; + +import { ResourceId, WorldResourceIdInstance } from "@latticexyz/world/src/WorldResourceId.sol"; +import { IWorldErrors } from "@latticexyz/world/src/IWorldErrors.sol"; +import { ResourceAccess } from "@latticexyz/world/src/codegen/tables/ResourceAccess.sol"; +import { NamespaceOwner } from "@latticexyz/world/src/codegen/tables/NamespaceOwner.sol"; + +/** + * @title AccessControlLib + * @dev Provides access control functions for checking permissions and ownership within a namespace. + * This library is functionally equivalent with the AccessControl library from world, + * but uses StoreSwitch instead of always reading from own storage. + */ +library AccessControlLib { + using WorldResourceIdInstance for ResourceId; + + /** + * @notice Checks if the caller has access to the given resource ID or its namespace. + * @param resourceId The resource ID to check access for. + * @param caller The address of the caller. + * @return true if the caller has access, false otherwise. + */ + function hasAccess(ResourceId resourceId, address caller) internal view returns (bool) { + return + // First check access based on the namespace. If caller has no namespace access, check access on the resource. + ResourceAccess.get(resourceId.getNamespaceId(), caller) || ResourceAccess.get(resourceId, caller); + } + + /** + * @notice Check for access at the given namespace or resource. + * @param resourceId The resource ID to check access for. + * @param caller The address of the caller. + * @dev Reverts with IWorldErrors.World_AccessDenied if access is denied. + */ + function requireAccess(ResourceId resourceId, address caller) internal view { + // Check if the given caller has access to the given namespace or name + if (!hasAccess(resourceId, caller)) { + revert IWorldErrors.World_AccessDenied(resourceId.toString(), caller); + } + } + + /** + * @notice Check for ownership of the namespace of the given resource ID. + * @dev Reverts with IWorldErrors.World_AccessDenied if caller is not owner of the namespace of the resource. + * @param resourceId The resource ID to check ownership for. + * @param caller The address of the caller. + */ + function requireOwner(ResourceId resourceId, address caller) internal view { + if (NamespaceOwner.get(resourceId.getNamespaceId()) != caller) { + revert IWorldErrors.World_AccessDenied(resourceId.toString(), caller); + } + } +} diff --git a/packages/world-modules/test/PuppetModule.t.sol b/packages/world-modules/test/PuppetModule.t.sol new file mode 100644 index 0000000000..3adf735d70 --- /dev/null +++ b/packages/world-modules/test/PuppetModule.t.sol @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.21; + +import { Test } from "forge-std/Test.sol"; +import { GasReporter } from "@latticexyz/gas-report/src/GasReporter.sol"; + +import { World } from "@latticexyz/world/src/World.sol"; +import { ResourceId, WorldResourceIdLib, WorldResourceIdInstance } from "@latticexyz/world/src/WorldResourceId.sol"; +import { System } from "@latticexyz/world/src/System.sol"; +import { RESOURCE_SYSTEM } from "@latticexyz/world/src/worldResourceTypes.sol"; + +import { IBaseWorld } from "@latticexyz/world/src/codegen/interfaces/IBaseWorld.sol"; +import { IWorldErrors } from "@latticexyz/world/src/IWorldErrors.sol"; +import { DELEGATION_CONTROL_INTERFACE_ID } from "@latticexyz/world/src/IDelegationControl.sol"; + +import { CoreModule } from "@latticexyz/world/src/modules/core/CoreModule.sol"; +import { Systems } from "@latticexyz/world/src/codegen/tables/Systems.sol"; + +import { PuppetModule } from "../src/modules/puppet/PuppetModule.sol"; +import { PuppetDelegationControl } from "../src/modules/puppet/PuppetDelegationControl.sol"; +import { Puppet } from "../src/modules/puppet/Puppet.sol"; +import { PuppetMaster } from "../src/modules/puppet/PuppetMaster.sol"; +import { PUPPET_DELEGATION } from "../src/modules/puppet/constants.sol"; +import { registerPuppet } from "../src/modules/puppet/registerPuppet.sol"; + +contract PuppetTestSystem is System, PuppetMaster { + event Hello(string message); + + function echoAndEmit(string memory message) public returns (string memory) { + puppet().log(Hello.selector, abi.encode(message)); + return message; + } + + function msgSender() public view returns (address) { + return _msgSender(); + } +} + +contract PuppetModuleTest is Test, GasReporter { + using WorldResourceIdInstance for ResourceId; + + event Hello(string msg); + + IBaseWorld private world; + ResourceId private systemId = + WorldResourceIdLib.encode({ typeId: RESOURCE_SYSTEM, namespace: "namespace", name: "testSystem" }); + PuppetTestSystem private puppet; + + function setUp() public { + world = IBaseWorld(address(new World())); + world.initialize(new CoreModule()); + world.installModule(new PuppetModule(), new bytes(0)); + + // Register a new system + PuppetTestSystem system = new PuppetTestSystem(); + world.registerSystem(systemId, system, true); + + // Connect the puppet + puppet = PuppetTestSystem(address(new Puppet(world, systemId))); + registerPuppet(world, systemId, address(puppet)); + } + + function testEmitOnPuppet() public { + vm.expectEmit(true, true, true, true); + emit Hello("hello world"); + string memory result = puppet.echoAndEmit("hello world"); + assertEq(result, "hello world"); + } + + function testMsgSender() public { + assertEq(puppet.msgSender(), address(this)); + } +} From 803b83ac66b655d703772f0eb398193bd6bc8fe1 Mon Sep 17 00:00:00 2001 From: alvrs Date: Wed, 18 Oct 2023 21:51:58 +0100 Subject: [PATCH 02/13] gas report --- packages/world-modules/src/index.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/world-modules/src/index.sol b/packages/world-modules/src/index.sol index e88b495bf3..64a418403f 100644 --- a/packages/world-modules/src/index.sol +++ b/packages/world-modules/src/index.sol @@ -9,3 +9,4 @@ import { UsedKeysIndex, UsedKeysIndexTableId } from "./modules/keysintable/table 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 { PuppetRegistry } from "./modules/puppet/tables/PuppetRegistry.sol"; From a6bdbbdd06521fcaed4005768bca871316c7661d Mon Sep 17 00:00:00 2001 From: alvrs Date: Thu, 19 Oct 2023 20:10:37 +0100 Subject: [PATCH 03/13] registerPuppet -> createPuppet --- .../modules/puppet/{registerPuppet.sol => createPuppet.sol} | 6 ++++-- packages/world-modules/test/PuppetModule.t.sol | 5 ++--- 2 files changed, 6 insertions(+), 5 deletions(-) rename packages/world-modules/src/modules/puppet/{registerPuppet.sol => createPuppet.sol} (76%) diff --git a/packages/world-modules/src/modules/puppet/registerPuppet.sol b/packages/world-modules/src/modules/puppet/createPuppet.sol similarity index 76% rename from packages/world-modules/src/modules/puppet/registerPuppet.sol rename to packages/world-modules/src/modules/puppet/createPuppet.sol index dd097c7c1b..0ef485f3c5 100644 --- a/packages/world-modules/src/modules/puppet/registerPuppet.sol +++ b/packages/world-modules/src/modules/puppet/createPuppet.sol @@ -5,13 +5,15 @@ import { IBaseWorld } from "@latticexyz/world/src/codegen/interfaces/IBaseWorld. import { WorldResourceIdInstance } from "@latticexyz/world/src/WorldResourceId.sol"; import { PUPPET_DELEGATION } from "./constants.sol"; import { PuppetDelegationControl } from "./PuppetDelegationControl.sol"; +import { Puppet } from "./Puppet.sol"; using WorldResourceIdInstance for ResourceId; -function registerPuppet(IBaseWorld world, ResourceId systemId, address puppet) { +function createPuppet(IBaseWorld world, ResourceId systemId) returns (address puppet) { + puppet = address(new Puppet(world, systemId)); world.registerNamespaceDelegation( systemId.getNamespaceId(), PUPPET_DELEGATION, - abi.encodeCall(PuppetDelegationControl.initDelegation, (systemId, address(puppet))) + abi.encodeCall(PuppetDelegationControl.initDelegation, (systemId, puppet)) ); } diff --git a/packages/world-modules/test/PuppetModule.t.sol b/packages/world-modules/test/PuppetModule.t.sol index 3adf735d70..7e29308620 100644 --- a/packages/world-modules/test/PuppetModule.t.sol +++ b/packages/world-modules/test/PuppetModule.t.sol @@ -21,7 +21,7 @@ import { PuppetDelegationControl } from "../src/modules/puppet/PuppetDelegationC import { Puppet } from "../src/modules/puppet/Puppet.sol"; import { PuppetMaster } from "../src/modules/puppet/PuppetMaster.sol"; import { PUPPET_DELEGATION } from "../src/modules/puppet/constants.sol"; -import { registerPuppet } from "../src/modules/puppet/registerPuppet.sol"; +import { createPuppet } from "../src/modules/puppet/createPuppet.sol"; contract PuppetTestSystem is System, PuppetMaster { event Hello(string message); @@ -56,8 +56,7 @@ contract PuppetModuleTest is Test, GasReporter { world.registerSystem(systemId, system, true); // Connect the puppet - puppet = PuppetTestSystem(address(new Puppet(world, systemId))); - registerPuppet(world, systemId, address(puppet)); + puppet = PuppetTestSystem(createPuppet(world, systemId)); } function testEmitOnPuppet() public { From 2ecfa3dcaec6e31c6abcca1d65455c09cdd1ee1b Mon Sep 17 00:00:00 2001 From: alvrs Date: Fri, 20 Oct 2023 13:15:42 +0100 Subject: [PATCH 04/13] add puppet factory --- .../puppet/PuppetDelegationControl.sol | 9 ------- .../modules/puppet/PuppetFactorySystem.sol | 25 +++++++++++++++++++ .../src/modules/puppet/PuppetMaster.sol | 2 +- .../src/modules/puppet/PuppetModule.sol | 7 ++++-- .../src/modules/puppet/constants.sol | 4 +++ .../src/modules/puppet/createPuppet.sol | 16 +++++++----- 6 files changed, 45 insertions(+), 18 deletions(-) create mode 100644 packages/world-modules/src/modules/puppet/PuppetFactorySystem.sol diff --git a/packages/world-modules/src/modules/puppet/PuppetDelegationControl.sol b/packages/world-modules/src/modules/puppet/PuppetDelegationControl.sol index bf67a50792..7011d8a4b3 100644 --- a/packages/world-modules/src/modules/puppet/PuppetDelegationControl.sol +++ b/packages/world-modules/src/modules/puppet/PuppetDelegationControl.sol @@ -15,13 +15,4 @@ contract PuppetDelegationControl is DelegationControl { address puppet = _msgSender(); return PuppetRegistry.get(PUPPET_TABLE_ID, systemId) == puppet; } - - /** - * Initialize a delegation by storing the mapping from systemId to puppet - */ - function initDelegation(ResourceId systemId, address puppet) public { - // Require the caller to be the owner of the system - AccessControlLib.requireOwner(systemId, _msgSender()); - PuppetRegistry.set(PUPPET_TABLE_ID, systemId, puppet); - } } diff --git a/packages/world-modules/src/modules/puppet/PuppetFactorySystem.sol b/packages/world-modules/src/modules/puppet/PuppetFactorySystem.sol new file mode 100644 index 0000000000..70ead9e57b --- /dev/null +++ b/packages/world-modules/src/modules/puppet/PuppetFactorySystem.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.21; + +import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; +import { System } from "@latticexyz/world/src/System.sol"; +import { IBaseWorld } from "@latticexyz/world/src/codegen/interfaces/IBaseWorld.sol"; + +import { AccessControlLib } from "../../utils/AccessControlLib.sol"; + +import { PuppetRegistry } from "./tables/PuppetRegistry.sol"; +import { Puppet } from "./Puppet.sol"; +import { PUPPET_TABLE_ID } from "./constants.sol"; + +contract PuppetFactorySystem is System { + function createPuppet(ResourceId systemId) public returns (address puppet) { + // Only the owner of a system can create a puppet for it + AccessControlLib.requireOwner(systemId, _msgSender()); + + // Deploy a new puppet contract + puppet = address(new Puppet(IBaseWorld(_world()), systemId)); + + // Register the puppet + PuppetRegistry.set(PUPPET_TABLE_ID, systemId, puppet); + } +} diff --git a/packages/world-modules/src/modules/puppet/PuppetMaster.sol b/packages/world-modules/src/modules/puppet/PuppetMaster.sol index 4659337772..8c4748bbcd 100644 --- a/packages/world-modules/src/modules/puppet/PuppetMaster.sol +++ b/packages/world-modules/src/modules/puppet/PuppetMaster.sol @@ -9,7 +9,7 @@ import { Puppet } from "./Puppet.sol"; contract PuppetMaster { error PuppetMaster_NoPuppet(address systemAddress, ResourceId systemId); - function puppet() internal returns (Puppet) { + function puppet() internal view returns (Puppet) { ResourceId systemId = SystemRegistry.getSystemId(address(this)); address puppetAddress = PuppetRegistry.get(PUPPET_TABLE_ID, systemId); if (puppetAddress == address(0)) revert PuppetMaster_NoPuppet(address(this), systemId); diff --git a/packages/world-modules/src/modules/puppet/PuppetModule.sol b/packages/world-modules/src/modules/puppet/PuppetModule.sol index cee2e6a8b4..777aed1d44 100644 --- a/packages/world-modules/src/modules/puppet/PuppetModule.sol +++ b/packages/world-modules/src/modules/puppet/PuppetModule.sol @@ -7,8 +7,9 @@ import { Module } from "@latticexyz/world/src/Module.sol"; import { WorldContextConsumer } from "@latticexyz/world/src/WorldContext.sol"; import { revertWithBytes } from "@latticexyz/world/src/revertWithBytes.sol"; +import { PuppetFactorySystem } from "./PuppetFactorySystem.sol"; import { PuppetDelegationControl } from "./PuppetDelegationControl.sol"; -import { MODULE_NAME, PUPPET_DELEGATION, PUPPET_TABLE_ID } from "./constants.sol"; +import { MODULE_NAME, PUPPET_DELEGATION, PUPPET_FACTORY, PUPPET_TABLE_ID } from "./constants.sol"; import { PuppetRegistry } from "./tables/PuppetRegistry.sol"; @@ -17,6 +18,7 @@ import { PuppetRegistry } from "./tables/PuppetRegistry.sol"; */ contract PuppetModule is Module { PuppetDelegationControl private immutable puppetDelegationControl = new PuppetDelegationControl(); + PuppetFactorySystem private immutable puppetFactorySystem = new PuppetFactorySystem(); function getName() public pure returns (bytes16) { return MODULE_NAME; @@ -41,7 +43,8 @@ contract PuppetModule is Module { // Register table PuppetRegistry.register(PUPPET_TABLE_ID); - // Register system + // Register puppet factory and delegation control + world.registerSystem(PUPPET_FACTORY, puppetFactorySystem, true); world.registerSystem(PUPPET_DELEGATION, puppetDelegationControl, true); } } diff --git a/packages/world-modules/src/modules/puppet/constants.sol b/packages/world-modules/src/modules/puppet/constants.sol index ac6b6fbe40..c441dd03b9 100644 --- a/packages/world-modules/src/modules/puppet/constants.sol +++ b/packages/world-modules/src/modules/puppet/constants.sol @@ -13,6 +13,10 @@ ResourceId constant PUPPET_DELEGATION = ResourceId.wrap( bytes32(abi.encodePacked(RESOURCE_SYSTEM, NAMESPACE, bytes16("Delegation"))) ); +ResourceId constant PUPPET_FACTORY = ResourceId.wrap( + bytes32(abi.encodePacked(RESOURCE_SYSTEM, NAMESPACE, bytes16("Factory"))) +); + ResourceId constant PUPPET_TABLE_ID = ResourceId.wrap( bytes32(abi.encodePacked(RESOURCE_TABLE, NAMESPACE, bytes16("PuppetRegistry"))) ); diff --git a/packages/world-modules/src/modules/puppet/createPuppet.sol b/packages/world-modules/src/modules/puppet/createPuppet.sol index 0ef485f3c5..54311284c5 100644 --- a/packages/world-modules/src/modules/puppet/createPuppet.sol +++ b/packages/world-modules/src/modules/puppet/createPuppet.sol @@ -3,17 +3,21 @@ pragma solidity >=0.8.21; import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; import { IBaseWorld } from "@latticexyz/world/src/codegen/interfaces/IBaseWorld.sol"; import { WorldResourceIdInstance } from "@latticexyz/world/src/WorldResourceId.sol"; -import { PUPPET_DELEGATION } from "./constants.sol"; +import { PUPPET_DELEGATION, PUPPET_FACTORY } from "./constants.sol"; import { PuppetDelegationControl } from "./PuppetDelegationControl.sol"; import { Puppet } from "./Puppet.sol"; +import { PuppetFactorySystem } from "./PuppetFactorySystem.sol"; using WorldResourceIdInstance for ResourceId; +/** + * This free function can be used to create a puppet and register it with the puppet delegation control. + * Since it is inlined in the caller's context, the calls originate from the caller's address. + */ function createPuppet(IBaseWorld world, ResourceId systemId) returns (address puppet) { - puppet = address(new Puppet(world, systemId)); - world.registerNamespaceDelegation( - systemId.getNamespaceId(), - PUPPET_DELEGATION, - abi.encodeCall(PuppetDelegationControl.initDelegation, (systemId, puppet)) + puppet = abi.decode( + world.call(PUPPET_FACTORY, abi.encodeCall(PuppetFactorySystem.createPuppet, (systemId))), + (address) ); + world.registerNamespaceDelegation(systemId.getNamespaceId(), PUPPET_DELEGATION, new bytes(0)); } From 3124be1624838ffa3ef55c0fea7b7858497098ac Mon Sep 17 00:00:00 2001 From: alvrs Date: Mon, 30 Oct 2023 21:27:08 +0100 Subject: [PATCH 05/13] ignore PuppetFactorySystem in world-modules IBaseWorld interface --- packages/world-modules/mud.config.ts | 2 +- .../src/interfaces/IPuppetFactorySystem.sol | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 packages/world-modules/src/interfaces/IPuppetFactorySystem.sol diff --git a/packages/world-modules/mud.config.ts b/packages/world-modules/mud.config.ts index dfff9fbda9..9701c93ab2 100644 --- a/packages/world-modules/mud.config.ts +++ b/packages/world-modules/mud.config.ts @@ -107,5 +107,5 @@ export default mudConfig({ }, }, - excludeSystems: ["UniqueEntitySystem"], + excludeSystems: ["UniqueEntitySystem", "PuppetFactorySystem"], }); diff --git a/packages/world-modules/src/interfaces/IPuppetFactorySystem.sol b/packages/world-modules/src/interfaces/IPuppetFactorySystem.sol new file mode 100644 index 0000000000..6166baa98b --- /dev/null +++ b/packages/world-modules/src/interfaces/IPuppetFactorySystem.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.21; + +/* Autogenerated file. Do not edit manually. */ + +import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; + +/** + * @title IPuppetFactorySystem + * @dev This interface is automatically generated from the corresponding system contract. Do not edit manually. + */ +interface IPuppetFactorySystem { + function createPuppet(ResourceId systemId) external returns (address puppet); +} From 276b1f0e6538fe29fe0a7b28f7dc06a99d5066a8 Mon Sep 17 00:00:00 2001 From: alvarius Date: Mon, 30 Oct 2023 21:44:00 +0100 Subject: [PATCH 06/13] Create happy-pants-try.md --- .changeset/happy-pants-try.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .changeset/happy-pants-try.md diff --git a/.changeset/happy-pants-try.md b/.changeset/happy-pants-try.md new file mode 100644 index 0000000000..de22c2e9b0 --- /dev/null +++ b/.changeset/happy-pants-try.md @@ -0,0 +1,22 @@ +--- +"@latticexyz/world-modules": minor +--- + +Adds the `PuppetModule` to `@latticexyz/world-modules`. The puppet pattern allows an external contract to be registered as an external interface for a MUD system. +This allows standards like `ERC20` (that require a specific interface and events to be emitted by a unique contract) to be implemented inside a MUD World. + +The puppet serves as a proxy, forwarding all calls to the implementation system (also called the "puppet master"). +The "puppet master" system can emit events from the puppet contract. + +```solidity +import { PuppetModule } from "@latticexyz/world-modules/src/modules/puppet/PuppetModule.sol"; +import { createPuppet } from "@latticexyz/world-modules/src/modules/puppet/createPuppet.sol"; + +// Install the puppet module +world.installModule(new PuppetModule(), new bytes(0)); + +// Register a new puppet for any system +// The system must implement the `CustomInterface`, +// and the caller must own the system's namespace +CustomInterface puppet = CustomInterface(createPuppet(world, )); +``` From 5afee8b5940465c1dc34f6035a4aab2a7a19957f Mon Sep 17 00:00:00 2001 From: alvrs Date: Tue, 31 Oct 2023 22:02:21 +0100 Subject: [PATCH 07/13] use system switch in createPuppet --- .../src/modules/puppet/createPuppet.sol | 16 ++++++++++++---- packages/world-modules/test/PuppetModule.t.sol | 4 +++- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/packages/world-modules/src/modules/puppet/createPuppet.sol b/packages/world-modules/src/modules/puppet/createPuppet.sol index 54311284c5..0ca446e522 100644 --- a/packages/world-modules/src/modules/puppet/createPuppet.sol +++ b/packages/world-modules/src/modules/puppet/createPuppet.sol @@ -1,23 +1,31 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.21; import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; -import { IBaseWorld } from "@latticexyz/world/src/codegen/interfaces/IBaseWorld.sol"; +import { IWorldRegistrationSystem } from "@latticexyz/world/src/codegen/interfaces/IWorldRegistrationSystem.sol"; import { WorldResourceIdInstance } from "@latticexyz/world/src/WorldResourceId.sol"; import { PUPPET_DELEGATION, PUPPET_FACTORY } from "./constants.sol"; import { PuppetDelegationControl } from "./PuppetDelegationControl.sol"; import { Puppet } from "./Puppet.sol"; import { PuppetFactorySystem } from "./PuppetFactorySystem.sol"; +import { SystemSwitch } from "../../utils/SystemSwitch.sol"; using WorldResourceIdInstance for ResourceId; /** * This free function can be used to create a puppet and register it with the puppet delegation control. * Since it is inlined in the caller's context, the calls originate from the caller's address. + * The function expects to be called from a World context + * (i.e. `StoreSwitch.setStoreAddress(world)` is set up in the calling contract) */ -function createPuppet(IBaseWorld world, ResourceId systemId) returns (address puppet) { +function createPuppet(ResourceId systemId) returns (address puppet) { puppet = abi.decode( - world.call(PUPPET_FACTORY, abi.encodeCall(PuppetFactorySystem.createPuppet, (systemId))), + SystemSwitch.call(PUPPET_FACTORY, abi.encodeCall(PuppetFactorySystem.createPuppet, (systemId))), (address) ); - world.registerNamespaceDelegation(systemId.getNamespaceId(), PUPPET_DELEGATION, new bytes(0)); + SystemSwitch.call( + abi.encodeCall( + IWorldRegistrationSystem.registerNamespaceDelegation, + (systemId.getNamespaceId(), PUPPET_DELEGATION, new bytes(0)) + ) + ); } diff --git a/packages/world-modules/test/PuppetModule.t.sol b/packages/world-modules/test/PuppetModule.t.sol index 7e29308620..d601da0b30 100644 --- a/packages/world-modules/test/PuppetModule.t.sol +++ b/packages/world-modules/test/PuppetModule.t.sol @@ -3,6 +3,7 @@ pragma solidity >=0.8.21; import { Test } from "forge-std/Test.sol"; import { GasReporter } from "@latticexyz/gas-report/src/GasReporter.sol"; +import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol"; import { World } from "@latticexyz/world/src/World.sol"; import { ResourceId, WorldResourceIdLib, WorldResourceIdInstance } from "@latticexyz/world/src/WorldResourceId.sol"; @@ -50,13 +51,14 @@ contract PuppetModuleTest is Test, GasReporter { world = IBaseWorld(address(new World())); world.initialize(new CoreModule()); world.installModule(new PuppetModule(), new bytes(0)); + StoreSwitch.setStoreAddress(address(world)); // Register a new system PuppetTestSystem system = new PuppetTestSystem(); world.registerSystem(systemId, system, true); // Connect the puppet - puppet = PuppetTestSystem(createPuppet(world, systemId)); + puppet = PuppetTestSystem(createPuppet(systemId)); } function testEmitOnPuppet() public { From 2bd978b7b62877e766707d043c674a5695695e63 Mon Sep 17 00:00:00 2001 From: alvarius Date: Tue, 31 Oct 2023 22:04:10 +0100 Subject: [PATCH 08/13] Update .changeset/happy-pants-try.md --- .changeset/happy-pants-try.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/happy-pants-try.md b/.changeset/happy-pants-try.md index de22c2e9b0..3f2a70fc4d 100644 --- a/.changeset/happy-pants-try.md +++ b/.changeset/happy-pants-try.md @@ -2,7 +2,7 @@ "@latticexyz/world-modules": minor --- -Adds the `PuppetModule` to `@latticexyz/world-modules`. The puppet pattern allows an external contract to be registered as an external interface for a MUD system. +Added the `PuppetModule` to `@latticexyz/world-modules`. The puppet pattern allows an external contract to be registered as an external interface for a MUD system. This allows standards like `ERC20` (that require a specific interface and events to be emitted by a unique contract) to be implemented inside a MUD World. The puppet serves as a proxy, forwarding all calls to the implementation system (also called the "puppet master"). From d6cc57e203c886d4043f33f6497db11569c066d2 Mon Sep 17 00:00:00 2001 From: alvrs Date: Tue, 31 Oct 2023 22:08:39 +0100 Subject: [PATCH 09/13] add helper functions for turning values into topics for puppet.log --- packages/world-modules/src/modules/puppet/utils.sol | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 packages/world-modules/src/modules/puppet/utils.sol diff --git a/packages/world-modules/src/modules/puppet/utils.sol b/packages/world-modules/src/modules/puppet/utils.sol new file mode 100644 index 0000000000..03028b4a99 --- /dev/null +++ b/packages/world-modules/src/modules/puppet/utils.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.21; + +function toTopic(address value) pure returns (bytes32) { + return bytes32(uint256(uint160(value))); +} + +function toTopic(uint256 value) pure returns (bytes32) { + return bytes32(value); +} From 6efde97defa5031a103aa6a9c250a82478ed32da Mon Sep 17 00:00:00 2001 From: alvarius Date: Tue, 31 Oct 2023 22:12:01 +0100 Subject: [PATCH 10/13] Apply suggestions from code review --- .changeset/happy-pants-try.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.changeset/happy-pants-try.md b/.changeset/happy-pants-try.md index 3f2a70fc4d..06a59e44f3 100644 --- a/.changeset/happy-pants-try.md +++ b/.changeset/happy-pants-try.md @@ -11,12 +11,16 @@ The "puppet master" system can emit events from the puppet contract. ```solidity import { PuppetModule } from "@latticexyz/world-modules/src/modules/puppet/PuppetModule.sol"; import { createPuppet } from "@latticexyz/world-modules/src/modules/puppet/createPuppet.sol"; +import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol"; // Install the puppet module world.installModule(new PuppetModule(), new bytes(0)); +// Set up the `StoreSwitch` context if not calling from within a `World` context already +StoreSwitch.setStoreAddress(address(world)); + // Register a new puppet for any system // The system must implement the `CustomInterface`, // and the caller must own the system's namespace -CustomInterface puppet = CustomInterface(createPuppet(world, )); +CustomInterface puppet = CustomInterface(createPuppet()); ``` From d46722adf5440a3ce1ae76acaa4ab95ee0c920f4 Mon Sep 17 00:00:00 2001 From: alvrs Date: Tue, 31 Oct 2023 22:18:47 +0100 Subject: [PATCH 11/13] revert using system switch --- .../src/modules/puppet/createPuppet.sol | 16 ++++------------ packages/world-modules/test/PuppetModule.t.sol | 4 +--- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/packages/world-modules/src/modules/puppet/createPuppet.sol b/packages/world-modules/src/modules/puppet/createPuppet.sol index 0ca446e522..54311284c5 100644 --- a/packages/world-modules/src/modules/puppet/createPuppet.sol +++ b/packages/world-modules/src/modules/puppet/createPuppet.sol @@ -1,31 +1,23 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.21; import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; -import { IWorldRegistrationSystem } from "@latticexyz/world/src/codegen/interfaces/IWorldRegistrationSystem.sol"; +import { IBaseWorld } from "@latticexyz/world/src/codegen/interfaces/IBaseWorld.sol"; import { WorldResourceIdInstance } from "@latticexyz/world/src/WorldResourceId.sol"; import { PUPPET_DELEGATION, PUPPET_FACTORY } from "./constants.sol"; import { PuppetDelegationControl } from "./PuppetDelegationControl.sol"; import { Puppet } from "./Puppet.sol"; import { PuppetFactorySystem } from "./PuppetFactorySystem.sol"; -import { SystemSwitch } from "../../utils/SystemSwitch.sol"; using WorldResourceIdInstance for ResourceId; /** * This free function can be used to create a puppet and register it with the puppet delegation control. * Since it is inlined in the caller's context, the calls originate from the caller's address. - * The function expects to be called from a World context - * (i.e. `StoreSwitch.setStoreAddress(world)` is set up in the calling contract) */ -function createPuppet(ResourceId systemId) returns (address puppet) { +function createPuppet(IBaseWorld world, ResourceId systemId) returns (address puppet) { puppet = abi.decode( - SystemSwitch.call(PUPPET_FACTORY, abi.encodeCall(PuppetFactorySystem.createPuppet, (systemId))), + world.call(PUPPET_FACTORY, abi.encodeCall(PuppetFactorySystem.createPuppet, (systemId))), (address) ); - SystemSwitch.call( - abi.encodeCall( - IWorldRegistrationSystem.registerNamespaceDelegation, - (systemId.getNamespaceId(), PUPPET_DELEGATION, new bytes(0)) - ) - ); + world.registerNamespaceDelegation(systemId.getNamespaceId(), PUPPET_DELEGATION, new bytes(0)); } diff --git a/packages/world-modules/test/PuppetModule.t.sol b/packages/world-modules/test/PuppetModule.t.sol index d601da0b30..7e29308620 100644 --- a/packages/world-modules/test/PuppetModule.t.sol +++ b/packages/world-modules/test/PuppetModule.t.sol @@ -3,7 +3,6 @@ pragma solidity >=0.8.21; import { Test } from "forge-std/Test.sol"; import { GasReporter } from "@latticexyz/gas-report/src/GasReporter.sol"; -import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol"; import { World } from "@latticexyz/world/src/World.sol"; import { ResourceId, WorldResourceIdLib, WorldResourceIdInstance } from "@latticexyz/world/src/WorldResourceId.sol"; @@ -51,14 +50,13 @@ contract PuppetModuleTest is Test, GasReporter { world = IBaseWorld(address(new World())); world.initialize(new CoreModule()); world.installModule(new PuppetModule(), new bytes(0)); - StoreSwitch.setStoreAddress(address(world)); // Register a new system PuppetTestSystem system = new PuppetTestSystem(); world.registerSystem(systemId, system, true); // Connect the puppet - puppet = PuppetTestSystem(createPuppet(systemId)); + puppet = PuppetTestSystem(createPuppet(world, systemId)); } function testEmitOnPuppet() public { From 235d571f6fd1ff9a216da59a3bbe3d360b3aeaf2 Mon Sep 17 00:00:00 2001 From: alvrs Date: Tue, 31 Oct 2023 22:19:56 +0100 Subject: [PATCH 12/13] revert change to changeset --- .changeset/happy-pants-try.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.changeset/happy-pants-try.md b/.changeset/happy-pants-try.md index 06a59e44f3..3f2a70fc4d 100644 --- a/.changeset/happy-pants-try.md +++ b/.changeset/happy-pants-try.md @@ -11,16 +11,12 @@ The "puppet master" system can emit events from the puppet contract. ```solidity import { PuppetModule } from "@latticexyz/world-modules/src/modules/puppet/PuppetModule.sol"; import { createPuppet } from "@latticexyz/world-modules/src/modules/puppet/createPuppet.sol"; -import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol"; // Install the puppet module world.installModule(new PuppetModule(), new bytes(0)); -// Set up the `StoreSwitch` context if not calling from within a `World` context already -StoreSwitch.setStoreAddress(address(world)); - // Register a new puppet for any system // The system must implement the `CustomInterface`, // and the caller must own the system's namespace -CustomInterface puppet = CustomInterface(createPuppet()); +CustomInterface puppet = CustomInterface(createPuppet(world, )); ``` From 4af069de54e344d280dd45b04a3d1f3ad7f59982 Mon Sep 17 00:00:00 2001 From: alvarius Date: Wed, 1 Nov 2023 14:59:40 +0100 Subject: [PATCH 13/13] Apply suggestions from code review Co-authored-by: dk1a --- .../world-modules/src/modules/puppet/PuppetDelegationControl.sol | 1 - packages/world-modules/src/modules/puppet/PuppetMaster.sol | 1 + packages/world-modules/src/modules/puppet/PuppetModule.sol | 1 - packages/world-modules/src/modules/puppet/createPuppet.sol | 1 + 4 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/world-modules/src/modules/puppet/PuppetDelegationControl.sol b/packages/world-modules/src/modules/puppet/PuppetDelegationControl.sol index 7011d8a4b3..c60d5afa6d 100644 --- a/packages/world-modules/src/modules/puppet/PuppetDelegationControl.sol +++ b/packages/world-modules/src/modules/puppet/PuppetDelegationControl.sol @@ -3,7 +3,6 @@ pragma solidity >=0.8.21; import { DelegationControl } from "@latticexyz/world/src/DelegationControl.sol"; import { ResourceId } from "@latticexyz/world/src/WorldResourceId.sol"; -import { AccessControlLib } from "../../utils/AccessControlLib.sol"; import { PuppetRegistry } from "./tables/PuppetRegistry.sol"; import { PUPPET_TABLE_ID } from "./constants.sol"; diff --git a/packages/world-modules/src/modules/puppet/PuppetMaster.sol b/packages/world-modules/src/modules/puppet/PuppetMaster.sol index 8c4748bbcd..45e587bb0a 100644 --- a/packages/world-modules/src/modules/puppet/PuppetMaster.sol +++ b/packages/world-modules/src/modules/puppet/PuppetMaster.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.21; + import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; import { SystemRegistry } from "@latticexyz/world/src/codegen/tables/SystemRegistry.sol"; import { PuppetRegistry } from "./tables/PuppetRegistry.sol"; diff --git a/packages/world-modules/src/modules/puppet/PuppetModule.sol b/packages/world-modules/src/modules/puppet/PuppetModule.sol index 777aed1d44..ecf4fcfad4 100644 --- a/packages/world-modules/src/modules/puppet/PuppetModule.sol +++ b/packages/world-modules/src/modules/puppet/PuppetModule.sol @@ -4,7 +4,6 @@ pragma solidity >=0.8.21; import { IBaseWorld } from "@latticexyz/world/src/codegen/interfaces/IBaseWorld.sol"; import { Module } from "@latticexyz/world/src/Module.sol"; -import { WorldContextConsumer } from "@latticexyz/world/src/WorldContext.sol"; import { revertWithBytes } from "@latticexyz/world/src/revertWithBytes.sol"; import { PuppetFactorySystem } from "./PuppetFactorySystem.sol"; diff --git a/packages/world-modules/src/modules/puppet/createPuppet.sol b/packages/world-modules/src/modules/puppet/createPuppet.sol index 54311284c5..b00641a5b8 100644 --- a/packages/world-modules/src/modules/puppet/createPuppet.sol +++ b/packages/world-modules/src/modules/puppet/createPuppet.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.21; + import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; import { IBaseWorld } from "@latticexyz/world/src/codegen/interfaces/IBaseWorld.sol"; import { WorldResourceIdInstance } from "@latticexyz/world/src/WorldResourceId.sol";