Skip to content

Commit

Permalink
Merge branch 'main' into holic/world-system-libs
Browse files Browse the repository at this point in the history
  • Loading branch information
holic authored Feb 8, 2025
2 parents 9ebebb7 + b774ab2 commit 0de4532
Show file tree
Hide file tree
Showing 39 changed files with 281 additions and 696 deletions.
5 changes: 5 additions & 0 deletions .changeset/breezy-pandas-reflect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@latticexyz/explorer": patch
---

Table names in SQL queries are now automatically enclosed in double quotes by default, allowing support for special characters.
5 changes: 5 additions & 0 deletions .changeset/chatty-coats-destroy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@latticexyz/world-module-erc20": patch
---

Migrated from `store-consumer` to `world-consumer`.
5 changes: 5 additions & 0 deletions .changeset/shy-terms-walk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@latticexyz/world-consumer": patch
---

Renamed `store-consumer` package to `world-consumer`. The `world-consumer` package now only includes a single `WorldConsumer` contract that is bound to a `World`.
8 changes: 4 additions & 4 deletions docs/pages/world/modules/erc20.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ The advantage of doing this, rather than creating a separate [ERC-20 contract](h

The token contract can be seen as a hybrid system contract which contains functions directly callable from outside the world (ERC20 functions like `transfer`, `balanceOf`, etc), and restricted functions that must be called as a system function through the World (`mint`, `pause`, etc).

The ERC20Module receives the namespace, name and symbol of the token as parameters, and deploys the new token. Currently it installs a [default ERC20](https://github.com/latticexyz/mud/tree/main/packages/world-module-erc20/src/examples/ERC20WithWorld.sol) with the following features:
The ERC20Module receives the namespace, name and symbol of the token as parameters, and deploys the new token. Currently it installs a [default ERC20](https://github.com/latticexyz/mud/tree/main/packages/world-module-erc20/src/examples/ERC20PausableBurnable.sol) with the following features:

- ERC20Burnable: Allows users to burn their tokens (or the ones approved to them) using the `burn` and `burnFrom` function.
- ERC20Pausable: Supports pausing and unpausing token operations. This is combined with the `pause` and `unpause` public functions that can be called by addresses and systems with access to the token's namespace. Must be done through a World call.
Expand Down Expand Up @@ -70,7 +70,7 @@ import { IWorldCall } from "@latticexyz/world/src/IWorldKernel.sol";
import { WorldResourceIdLib } from "@latticexyz/world/src/WorldResourceId.sol";
import { SystemRegistry } from "@latticexyz/world/src/codegen/tables/SystemRegistry.sol";
import { ERC20Registry } from "@latticexyz/world-module-erc20/src/codegen/index.sol";
import { ERC20WithWorld as ERC20 } from "@latticexyz/world-module-erc20/src/examples/ERC20WithWorld.sol";
import { ERC20PausableBurnable as ERC20 } from "@latticexyz/world-module-erc20/src/examples/ERC20PausableBurnable.sol";
import { IWorld } from "../src/codegen/world/IWorld.sol";
Expand Down Expand Up @@ -98,7 +98,7 @@ contract ManageERC20 is Script {
// Get the ERC-20 token address
ResourceId namespaceResource = WorldResourceIdLib.encodeNamespace(bytes14("erc20Namespace"));
ResourceId erc20RegistryResource = WorldResourceIdLib.encode(RESOURCE_TABLE, "erc20-module", "ERC20_REGISTRY");
ResourceId erc20RegistryResource = WorldResourceIdLib.encode(RESOURCE_TABLE, "erc20-module", "ERC20Registry");
address tokenAddress = ERC20Registry.getTokenAddress(erc20RegistryResource, namespaceResource);
ResourceId tokenSystem = SystemRegistry.get(tokenAddress);
console.log("Token address", tokenAddress);
Expand Down Expand Up @@ -142,7 +142,7 @@ contract ManageERC20 is Script {
```solidity
// Get the ERC-20 token address
ResourceId namespaceResource = WorldResourceIdLib.encodeNamespace(bytes14("erc20Namespace"));
ResourceId erc20RegistryResource = WorldResourceIdLib.encode(RESOURCE_TABLE, "erc20-module", "ERC20_REGISTRY");
ResourceId erc20RegistryResource = WorldResourceIdLib.encode(RESOURCE_TABLE, "erc20-module", "ERC20Registry");
address tokenAddress = ERC20Registry.getTokenAddress(erc20RegistryResource, namespaceResource);
ResourceId tokenSystem = SystemRegistry.get(tokenAddress);
console.log("Token address", tokenAddress);
Expand Down
2 changes: 1 addition & 1 deletion docs/pages/world/reference/world-context.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ function _msgSender() public view virtual returns (address sender);
Extract the `msg.value` from the context appended to the calldata.

```solidity
function _msgValue() public pure returns (uint256 value);
function _msgValue() public view virtual returns (uint256 value);
```

**Returns**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export function Explorer() {
setQuery(`SELECT * FROM "${tableName}";`);
} else {
const columns = Object.keys(table.schema).map((column) => `"${column}"`);
setQuery(`SELECT ${columns.join(", ")} FROM ${tableName};`);
setQuery(`SELECT ${columns.join(", ")} FROM "${tableName}";`);
}
}
}, [chainId, setQuery, selectedTableId, table, worldAddress, prevSelectedTableId, query, indexer.type]);
Expand Down
11 changes: 0 additions & 11 deletions packages/store-consumer/README.md

This file was deleted.

10 changes: 0 additions & 10 deletions packages/store-consumer/src/experimental/Context.sol

This file was deleted.

24 changes: 0 additions & 24 deletions packages/store-consumer/src/experimental/StoreConsumer.sol

This file was deleted.

22 changes: 0 additions & 22 deletions packages/store-consumer/src/experimental/WithStore.sol

This file was deleted.

80 changes: 0 additions & 80 deletions packages/store-consumer/src/experimental/WithWorld.sol

This file was deleted.

4 changes: 2 additions & 2 deletions packages/store/ts/flattenStoreLogs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,8 @@ describe("flattenStoreLogs", async () => {
"Store_SetRecord store__ResourceIds (0x746200000000000000000000000000005465727261696e000000000000000000)",
"Store_SetRecord store__ResourceIds (0x737900000000000000000000000000004d6f766553797374656d000000000000)",
"Store_SetRecord world__Systems (0x737900000000000000000000000000004d6f766553797374656d000000000000)",
"Store_SetRecord world__SystemRegistry (0x0000000000000000000000006eb355773196079fe643ed78724140adb1f86c11)",
"Store_SetRecord world__ResourceAccess (0x6e73000000000000000000000000000000000000000000000000000000000000,0x0000000000000000000000006eb355773196079fe643ed78724140adb1f86c11)",
"Store_SetRecord world__SystemRegistry (0x000000000000000000000000cbcdc66f9301ccf30b6b46efba8a3015d332dc13)",
"Store_SetRecord world__ResourceAccess (0x6e73000000000000000000000000000000000000000000000000000000000000,0x000000000000000000000000cbcdc66f9301ccf30b6b46efba8a3015d332dc13)",
"Store_SetRecord world__FunctionSelector (0xb591186e00000000000000000000000000000000000000000000000000000000)",
"Store_SetRecord world__FunctionSignatur (0xb591186e00000000000000000000000000000000000000000000000000000000)",
"Store_SetRecord store__Tables (0x7462000000000000000000000000000043616c6c576974685369676e61747572)",
Expand Down
4 changes: 2 additions & 2 deletions packages/store/ts/getStoreLogs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,8 @@ describe("getStoreLogs", async () => {
"Store_SpliceStaticData store__ResourceIds (0x746200000000000000000000000000005465727261696e000000000000000000)",
"Store_SpliceStaticData store__ResourceIds (0x737900000000000000000000000000004d6f766553797374656d000000000000)",
"Store_SetRecord world__Systems (0x737900000000000000000000000000004d6f766553797374656d000000000000)",
"Store_SpliceStaticData world__SystemRegistry (0x0000000000000000000000006eb355773196079fe643ed78724140adb1f86c11)",
"Store_SpliceStaticData world__ResourceAccess (0x6e73000000000000000000000000000000000000000000000000000000000000,0x0000000000000000000000006eb355773196079fe643ed78724140adb1f86c11)",
"Store_SpliceStaticData world__SystemRegistry (0x000000000000000000000000cbcdc66f9301ccf30b6b46efba8a3015d332dc13)",
"Store_SpliceStaticData world__ResourceAccess (0x6e73000000000000000000000000000000000000000000000000000000000000,0x000000000000000000000000cbcdc66f9301ccf30b6b46efba8a3015d332dc13)",
"Store_SetRecord world__FunctionSelector (0xb591186e00000000000000000000000000000000000000000000000000000000)",
"Store_SetRecord world__FunctionSignatur (0xb591186e00000000000000000000000000000000000000000000000000000000)",
"Store_SetRecord world__FunctionSignatur (0xb591186e00000000000000000000000000000000000000000000000000000000)",
Expand Down
File renamed without changes.
File renamed without changes.
8 changes: 8 additions & 0 deletions packages/world-consumer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# World Consumer

> :warning: **Important note: these contracts have not been audited yet, so any production use is discouraged for now.**
The `WorldConsumer` contract allows contracts that inherit from it to be registered as systems while also supporting functions that can be called from outside of the world `World`.
It initializes the store and also registers the provided namespace in the provided World. It provides the `onlyWorld` and `onlyNamespace` modifiers, which can be used to restrict access to certain functions, only allowing calls that come from the world.

For examples of how it can be used in practice you can check the [examples directory](./src/examples/) and our [ERC20 World Module](../world-module-erc20/).
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@latticexyz/store-consumer",
"name": "@latticexyz/world-consumer",
"version": "2.2.19",
"description": "Store Consumer Contracts",
"description": "World Consumer Contracts",
"repository": {
"type": "git",
"url": "https://github.com/latticexyz/mud.git",
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { ResourceId } from "@latticexyz/store/src/ResourceId.sol";
import { IBaseWorld } from "@latticexyz/world/src/codegen/interfaces/IBaseWorld.sol";
import { NamespaceOwner } from "@latticexyz/world/src/codegen/tables/NamespaceOwner.sol";

import { WithWorld } from "../experimental/WithWorld.sol";
import { WorldConsumer } from "../experimental/WorldConsumer.sol";

interface IERC20 {
function transfer(address to, uint256 value) external returns (bool);
Expand All @@ -19,10 +19,10 @@ interface IERC20 {
* @dev Simple example of a Vault that allows accounts with namespace access to transfer its tokens out
* IMPORTANT: this contract expects an existing namespace
*/
contract SimpleVault is WithWorld {
contract SimpleVault is WorldConsumer {
error SimpleVault_TransferFailed();

constructor(IBaseWorld world, bytes14 namespace) WithWorld(world, namespace, false) {}
constructor(IBaseWorld world, bytes14 namespace) WorldConsumer(world, namespace, false) {}

// Only accounts with namespace access (e.g. namespace systems) can transfer the ERC20 tokens held by this contract
function transferTo(IERC20 token, address to, uint256 amount) external onlyWorld {
Expand Down
82 changes: 82 additions & 0 deletions packages/world-consumer/src/experimental/WorldConsumer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.24;

import { RESOURCE_TABLE, RESOURCE_OFFCHAIN_TABLE } from "@latticexyz/store/src/storeResourceTypes.sol";
import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol";
import { ResourceIds } from "@latticexyz/store/src/codegen/tables/ResourceIds.sol";
import { ResourceId } from "@latticexyz/store/src/ResourceId.sol";
import { ResourceAccess } from "@latticexyz/world/src/codegen/tables/ResourceAccess.sol";
import { IBaseWorld } from "@latticexyz/world/src/codegen/interfaces/IBaseWorld.sol";
import { WorldResourceIdLib } from "@latticexyz/world/src/WorldResourceId.sol";
import { WorldContextConsumer } from "@latticexyz/world/src/WorldContext.sol";
import { System } from "@latticexyz/world/src/System.sol";

abstract contract WorldConsumer is System {
bytes14 public immutable namespace;
ResourceId public immutable namespaceId;

error WorldConsumer_RootNamespaceNotAllowed(address worldAddress);
error WorldConsumer_NamespaceAlreadyExists(address worldAddress, bytes14 namespace);
error WorldConsumer_NamespaceDoesNotExists(address worldAddress, bytes14 namespace);
error WorldConsumer_CallerHasNoNamespaceAccess(address worldAddress, bytes14 namespace, address caller);
error WorldConsumer_CallerIsNotWorld(address worldAddress, address caller);
error WorldConsumer_ValueNotAllowed(address worldAddress);

modifier onlyWorld() {
address world = _world();
if (world != msg.sender) {
revert WorldConsumer_CallerIsNotWorld(world, msg.sender);
}
_;
}

modifier onlyNamespace() {
address world = _world();
if (world != msg.sender) {
revert WorldConsumer_CallerIsNotWorld(world, msg.sender);
}

// We use WorldContextConsumer directly as we already know the world is the caller
address sender = WorldContextConsumer._msgSender();
if (!ResourceAccess.get(namespaceId, sender)) {
revert WorldConsumer_CallerHasNoNamespaceAccess(world, namespace, sender);
}

_;
}

constructor(IBaseWorld _world, bytes14 _namespace, bool registerNamespace) {
address worldAddress = address(_world);
StoreSwitch.setStoreAddress(worldAddress);

if (_namespace == bytes14(0)) {
revert WorldConsumer_RootNamespaceNotAllowed(worldAddress);
}

namespace = _namespace;
namespaceId = WorldResourceIdLib.encodeNamespace(_namespace);
bool namespaceExists = ResourceIds.getExists(namespaceId);

if (registerNamespace) {
if (namespaceExists) {
revert WorldConsumer_NamespaceAlreadyExists(worldAddress, _namespace);
}
_world.registerNamespace(namespaceId);
} else if (!namespaceExists) {
revert WorldConsumer_NamespaceDoesNotExists(worldAddress, _namespace);
}
}

function _msgSender() public view virtual override returns (address sender) {
return _world() == msg.sender ? WorldContextConsumer._msgSender() : msg.sender;
}

function _msgValue() public view virtual override returns (uint256 value) {
address world = _world();
if (world != msg.sender) {
revert WorldConsumer_ValueNotAllowed(world);
}

return WorldContextConsumer._msgValue();
}
}
Loading

0 comments on commit 0de4532

Please sign in to comment.