-
Notifications
You must be signed in to change notification settings - Fork 194
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(world-modules): add puppet module (#1793)
Co-authored-by: dk1a <dk1a@protonmail.com>
- Loading branch information
Showing
15 changed files
with
671 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
--- | ||
"@latticexyz/world-modules": minor | ||
--- | ||
|
||
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"). | ||
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, <systemId>)); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
14 changes: 14 additions & 0 deletions
14
packages/world-modules/src/interfaces/IPuppetFactorySystem.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} | ||
} | ||
} |
17 changes: 17 additions & 0 deletions
17
packages/world-modules/src/modules/puppet/PuppetDelegationControl.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
// 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 { 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; | ||
} | ||
} |
25 changes: 25 additions & 0 deletions
25
packages/world-modules/src/modules/puppet/PuppetFactorySystem.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} |
19 changes: 19 additions & 0 deletions
19
packages/world-modules/src/modules/puppet/PuppetMaster.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
// 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 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); | ||
return Puppet(puppetAddress); | ||
} | ||
} |
49 changes: 49 additions & 0 deletions
49
packages/world-modules/src/modules/puppet/PuppetModule.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
// 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 { revertWithBytes } from "@latticexyz/world/src/revertWithBytes.sol"; | ||
|
||
import { PuppetFactorySystem } from "./PuppetFactorySystem.sol"; | ||
import { PuppetDelegationControl } from "./PuppetDelegationControl.sol"; | ||
import { MODULE_NAME, PUPPET_DELEGATION, PUPPET_FACTORY, 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(); | ||
PuppetFactorySystem private immutable puppetFactorySystem = new PuppetFactorySystem(); | ||
|
||
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 puppet factory and delegation control | ||
world.registerSystem(PUPPET_FACTORY, puppetFactorySystem, true); | ||
world.registerSystem(PUPPET_DELEGATION, puppetDelegationControl, true); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
// 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_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"))) | ||
); |
24 changes: 24 additions & 0 deletions
24
packages/world-modules/src/modules/puppet/createPuppet.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
// 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, 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 = abi.decode( | ||
world.call(PUPPET_FACTORY, abi.encodeCall(PuppetFactorySystem.createPuppet, (systemId))), | ||
(address) | ||
); | ||
world.registerNamespaceDelegation(systemId.getNamespaceId(), PUPPET_DELEGATION, new bytes(0)); | ||
} |
Oops, something went wrong.