Skip to content

Commit

Permalink
feat(world-modules): add ERC721 module (#1844)
Browse files Browse the repository at this point in the history
Co-authored-by: 0xhank <zeroxhank@gmail.com>
  • Loading branch information
alvrs and 0xhank committed Nov 1, 2023
1 parent 8363837 commit d7325e5
Show file tree
Hide file tree
Showing 29 changed files with 3,898 additions and 36 deletions.
21 changes: 21 additions & 0 deletions .changeset/gorgeous-swans-hide.md
@@ -0,0 +1,21 @@
---
"@latticexyz/world-modules": minor
---

Added the `ERC721Module` to `@latticexyz/world-modules`.
This module allows the registration of `ERC721` tokens in an existing World.

Important note: this module has not been audited yet, so any production use is discouraged for now.

````solidity
import { PuppetModule } from "@latticexyz/world-modules/src/modules/puppet/PuppetModule.sol";
import { ERC721MetadataData } from "@latticexyz/world-modules/src/modules/erc721-puppet/tables/ERC721Metadata.sol";
import { IERC721Mintable } from "@latticexyz/world-modules/src/modules/erc721-puppet/IERC721Mintable.sol";
import { registerERC721 } from "@latticexyz/world-modules/src/modules/erc721-puppet/registerERC721.sol";
// The ERC721 module requires the Puppet module to be installed first
world.installModule(new PuppetModule(), new bytes(0));
// After the Puppet module is installed, new ERC721 tokens can be registered
IERC721Mintable token = registerERC721(world, "myERC721", ERC721MetadataData({ name: "Token", symbol: "TKN", baseURI: "" }));```
````
86 changes: 78 additions & 8 deletions packages/world-modules/mud.config.ts
Expand Up @@ -107,11 +107,11 @@ export default mudConfig({
},
/************************************************************************
*
* ERC20 MODULE
* TOKEN TABLES (SHARED BY ERC20, ERC721)
*
************************************************************************/
Balances: {
directory: "modules/erc20-puppet/tables",
directory: "modules/tokens/tables",
keySchema: {
account: "address",
},
Expand All @@ -120,6 +120,21 @@ export default mudConfig({
},
tableIdArgument: true,
},
/************************************************************************
*
* ERC20 MODULE
*
************************************************************************/
ERC20Metadata: {
directory: "modules/erc20-puppet/tables",
keySchema: {},
valueSchema: {
decimals: "uint8",
name: "string",
symbol: "string",
},
tableIdArgument: true,
},
Allowances: {
directory: "modules/erc20-puppet/tables",
keySchema: {
Expand All @@ -139,18 +154,74 @@ export default mudConfig({
},
tableIdArgument: true,
},
Metadata: {
ERC20Registry: {
directory: "modules/erc20-puppet/tables",
keySchema: {
namespaceId: "ResourceId",
},
valueSchema: {
erc20Address: "address",
},
tableIdArgument: true,
},
/************************************************************************
*
* ERC721 MODULE
*
************************************************************************/
ERC721Metadata: {
directory: "modules/erc721-puppet/tables",
keySchema: {},
valueSchema: {
decimals: "uint8",
name: "string",
symbol: "string",
baseURI: "string",
},
tableIdArgument: true,
},
ERC20Registry: {
directory: "modules/erc20-puppet/tables",
TokenURI: {
directory: "modules/erc721-puppet/tables",
keySchema: {
tokenId: "uint256",
},
valueSchema: {
tokenURI: "string",
},
tableIdArgument: true,
},
Owners: {
directory: "modules/erc721-puppet/tables",
keySchema: {
tokenId: "uint256",
},
valueSchema: {
owner: "address",
},
tableIdArgument: true,
},
TokenApproval: {
directory: "modules/erc721-puppet/tables",
keySchema: {
tokenId: "uint256",
},
valueSchema: {
account: "address",
},
tableIdArgument: true,
},
OperatorApproval: {
directory: "modules/erc721-puppet/tables",
keySchema: {
owner: "address",
operator: "address",
},
valueSchema: {
approved: "bool",
},
tableIdArgument: true,
},
ERC721Registry: {
directory: "modules/erc721-puppet/tables",
keySchema: {
namespaceId: "ResourceId",
},
Expand All @@ -160,6 +231,5 @@ export default mudConfig({
tableIdArgument: true,
},
},

excludeSystems: ["UniqueEntitySystem", "PuppetFactorySystem", "ERC20System"],
excludeSystems: ["UniqueEntitySystem", "PuppetFactorySystem", "ERC20System", "ERC721System"],
});
10 changes: 8 additions & 2 deletions packages/world-modules/src/index.sol
Expand Up @@ -10,8 +10,14 @@ 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";
import { Balances } from "./modules/erc20-puppet/tables/Balances.sol";
import { Balances } from "./modules/tokens/tables/Balances.sol";
import { ERC20Metadata, ERC20MetadataData } from "./modules/erc20-puppet/tables/ERC20Metadata.sol";
import { Allowances } from "./modules/erc20-puppet/tables/Allowances.sol";
import { TotalSupply } from "./modules/erc20-puppet/tables/TotalSupply.sol";
import { Metadata, MetadataData } from "./modules/erc20-puppet/tables/Metadata.sol";
import { ERC20Registry } from "./modules/erc20-puppet/tables/ERC20Registry.sol";
import { ERC721Metadata, ERC721MetadataData } from "./modules/erc721-puppet/tables/ERC721Metadata.sol";
import { TokenURI } from "./modules/erc721-puppet/tables/TokenURI.sol";
import { Owners } from "./modules/erc721-puppet/tables/Owners.sol";
import { TokenApproval } from "./modules/erc721-puppet/tables/TokenApproval.sol";
import { OperatorApproval } from "./modules/erc721-puppet/tables/OperatorApproval.sol";
import { ERC721Registry } from "./modules/erc721-puppet/tables/ERC721Registry.sol";
10 changes: 5 additions & 5 deletions packages/world-modules/src/modules/erc20-puppet/ERC20Module.sol
Expand Up @@ -11,15 +11,15 @@ import { InstalledModules } from "@latticexyz/world/src/codegen/tables/Installed
import { Puppet } from "../puppet/Puppet.sol";
import { createPuppet } from "../puppet/createPuppet.sol";
import { MODULE_NAME as PUPPET_MODULE_NAME } from "../puppet/constants.sol";
import { Balances } from "../tokens/tables/Balances.sol";

import { MODULE_NAME, MODULE_NAMESPACE, MODULE_NAMESPACE_ID, ERC20_REGISTRY_TABLE_ID } from "./constants.sol";
import { _allowancesTableId, _balancesTableId, _metadataTableId, _erc20SystemId } from "./utils.sol";
import { ERC20System } from "./ERC20System.sol";

import { ERC20Registry } from "./tables/ERC20Registry.sol";
import { Balances } from "./tables/Balances.sol";
import { Allowances } from "./tables/Allowances.sol";
import { Metadata, MetadataData } from "./tables/Metadata.sol";
import { ERC20Metadata, ERC20MetadataData } from "./tables/ERC20Metadata.sol";

contract ERC20Module is Module {
error ERC20Module_InvalidNamespace(bytes14 namespace);
Expand All @@ -35,7 +35,7 @@ contract ERC20Module is Module {
// Register the tables
Allowances.register(_allowancesTableId(namespace));
Balances.register(_balancesTableId(namespace));
Metadata.register(_metadataTableId(namespace));
ERC20Metadata.register(_metadataTableId(namespace));

// Register a new ERC20System
IBaseWorld(_world()).registerSystem(_erc20SystemId(namespace), new ERC20System(), true);
Expand All @@ -55,7 +55,7 @@ contract ERC20Module is Module {
}

// Extract args
(bytes14 namespace, MetadataData memory metadata) = abi.decode(args, (bytes14, MetadataData));
(bytes14 namespace, ERC20MetadataData memory metadata) = abi.decode(args, (bytes14, ERC20MetadataData));

// Require the namespace to not be the module's namespace
if (namespace == MODULE_NAMESPACE) {
Expand All @@ -69,7 +69,7 @@ contract ERC20Module is Module {
_registerERC20(namespace);

// Initialize the Metadata
Metadata.set(_metadataTableId(namespace), metadata);
ERC20Metadata.set(_metadataTableId(namespace), metadata);

// Deploy and register the ERC20 puppet.
IBaseWorld world = IBaseWorld(_world());
Expand Down
11 changes: 5 additions & 6 deletions packages/world-modules/src/modules/erc20-puppet/ERC20System.sol
Expand Up @@ -6,18 +6,17 @@ import { System } from "@latticexyz/world/src/System.sol";
import { WorldResourceIdInstance } from "@latticexyz/world/src/WorldResourceId.sol";
import { NamespaceOwner } from "@latticexyz/world/src/codegen/tables/NamespaceOwner.sol";
import { SystemRegistry } from "@latticexyz/world/src/codegen/tables/SystemRegistry.sol";
import { ALLOWANCES_NAME, BALANCES_NAME, METADATA_NAME } from "./constants.sol";

import { AccessControlLib } from "../../utils/AccessControlLib.sol";
import { PuppetMaster } from "../puppet/PuppetMaster.sol";
import { toTopic } from "../puppet/utils.sol";
import { Balances } from "../tokens/tables/Balances.sol";

import { IERC20Mintable } from "./IERC20Mintable.sol";

import { Allowances } from "./tables/Allowances.sol";
import { Balances } from "./tables/Balances.sol";
import { TotalSupply } from "./tables/TotalSupply.sol";
import { Metadata } from "./tables/Metadata.sol";
import { ERC20Metadata } from "./tables/ERC20Metadata.sol";

import { _allowancesTableId, _balancesTableId, _totalSupplyTableId, _metadataTableId } from "./utils.sol";

Expand All @@ -28,15 +27,15 @@ contract ERC20System is System, IERC20Mintable, PuppetMaster {
* @dev Returns the name of the token.
*/
function name() public view virtual returns (string memory) {
return Metadata.getName(_metadataTableId(_namespace()));
return ERC20Metadata.getName(_metadataTableId(_namespace()));
}

/**
* @dev Returns the symbol of the token, usually a shorter version of the
* name.
*/
function symbol() public view virtual returns (string memory) {
return Metadata.getSymbol(_metadataTableId(_namespace()));
return ERC20Metadata.getSymbol(_metadataTableId(_namespace()));
}

/**
Expand All @@ -53,7 +52,7 @@ contract ERC20System is System, IERC20Mintable, PuppetMaster {
* {IERC20-balanceOf} and {IERC20-transfer}.
*/
function decimals() public view virtual returns (uint8) {
return Metadata.getDecimals(_metadataTableId(_namespace()));
return ERC20Metadata.getDecimals(_metadataTableId(_namespace()));
}

/**
Expand Down
3 changes: 2 additions & 1 deletion packages/world-modules/src/modules/erc20-puppet/IERC20.sol
Expand Up @@ -4,11 +4,12 @@
pragma solidity >=0.8.21;

import { IERC20Events } from "./IERC20Events.sol";
import { IERC20Errors } from "./IERC20Errors.sol";

/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 is IERC20Events {
interface IERC20 is IERC20Events, IERC20Errors {
/**
* @dev Returns the name of the token.
*/
Expand Down
Expand Up @@ -4,12 +4,11 @@
pragma solidity >=0.8.21;

import { IERC20 } from "./IERC20.sol";
import { IERC20Errors } from "./IERC20Errors.sol";

/**
* @dev Extending the ERC20 standard with permissioned mint and burn functions.
*/
interface IERC20Mintable is IERC20, IERC20Errors {
interface IERC20Mintable is IERC20 {
/**
* @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).
*
Expand Down
Expand Up @@ -9,7 +9,7 @@ import { ERC20Module } from "./ERC20Module.sol";
import { MODULE_NAMESPACE_ID, ERC20_REGISTRY_TABLE_ID } from "./constants.sol";
import { IERC20Mintable } from "./IERC20Mintable.sol";

import { MetadataData } from "./tables/Metadata.sol";
import { ERC20MetadataData } from "./tables/ERC20Metadata.sol";
import { ERC20Registry } from "./tables/ERC20Registry.sol";

/**
Expand All @@ -19,7 +19,7 @@ import { ERC20Registry } from "./tables/ERC20Registry.sol";
function registerERC20(
IBaseWorld world,
bytes14 namespace,
MetadataData memory metadata
ERC20MetadataData memory metadata
) returns (IERC20Mintable token) {
// Get the ERC20 module
ERC20Module erc20Module = ERC20Module(NamespaceOwner.get(MODULE_NAMESPACE_ID));
Expand Down
Expand Up @@ -24,13 +24,13 @@ FieldLayout constant _fieldLayout = FieldLayout.wrap(
0x0001010201000000000000000000000000000000000000000000000000000000
);

struct MetadataData {
struct ERC20MetadataData {
uint8 decimals;
string name;
string symbol;
}

library Metadata {
library ERC20Metadata {
/**
* @notice Get the table values' field layout.
* @return _fieldLayout The field layout for the table.
Expand Down Expand Up @@ -432,7 +432,7 @@ library Metadata {
/**
* @notice Get the full data.
*/
function get(ResourceId _tableId) internal view returns (MetadataData memory _table) {
function get(ResourceId _tableId) internal view returns (ERC20MetadataData memory _table) {
bytes32[] memory _keyTuple = new bytes32[](0);

(bytes memory _staticData, PackedCounter _encodedLengths, bytes memory _dynamicData) = StoreSwitch.getRecord(
Expand All @@ -446,7 +446,7 @@ library Metadata {
/**
* @notice Get the full data.
*/
function _get(ResourceId _tableId) internal view returns (MetadataData memory _table) {
function _get(ResourceId _tableId) internal view returns (ERC20MetadataData memory _table) {
bytes32[] memory _keyTuple = new bytes32[](0);

(bytes memory _staticData, PackedCounter _encodedLengths, bytes memory _dynamicData) = StoreCore.getRecord(
Expand Down Expand Up @@ -488,7 +488,7 @@ library Metadata {
/**
* @notice Set the full data using the data struct.
*/
function set(ResourceId _tableId, MetadataData memory _table) internal {
function set(ResourceId _tableId, ERC20MetadataData memory _table) internal {
bytes memory _staticData = encodeStatic(_table.decimals);

PackedCounter _encodedLengths = encodeLengths(_table.name, _table.symbol);
Expand All @@ -502,7 +502,7 @@ library Metadata {
/**
* @notice Set the full data using the data struct.
*/
function _set(ResourceId _tableId, MetadataData memory _table) internal {
function _set(ResourceId _tableId, ERC20MetadataData memory _table) internal {
bytes memory _staticData = encodeStatic(_table.decimals);

PackedCounter _encodedLengths = encodeLengths(_table.name, _table.symbol);
Expand Down Expand Up @@ -551,7 +551,7 @@ library Metadata {
bytes memory _staticData,
PackedCounter _encodedLengths,
bytes memory _dynamicData
) internal pure returns (MetadataData memory _table) {
) internal pure returns (ERC20MetadataData memory _table) {
(_table.decimals) = decodeStatic(_staticData);

(_table.name, _table.symbol) = decodeDynamic(_encodedLengths, _dynamicData);
Expand Down

0 comments on commit d7325e5

Please sign in to comment.