Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(world-modules): add ERC721 module #1844

Merged
merged 27 commits into from Nov 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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";
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
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()));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't feel strongly but wondering if these changes get moved to the ERC20 PR

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I generally agree, but in this case i'd prefer to not do the refactor for time reasons. We could merge the erc721 PR into the erc20 PR instead of merging both into main individually

}

/**
Expand Down
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