From 04dbad896374d4f1cb3f6626336504dbea34d771 Mon Sep 17 00:00:00 2001 From: alvrs Date: Thu, 19 Oct 2023 21:30:08 +0100 Subject: [PATCH 01/27] wip --- packages/world-modules/mud.config.ts | 86 +- packages/world-modules/src/index.sol | 10 +- .../src/modules/erc20-puppet/ERC20Module.sol | 10 +- .../src/modules/erc20-puppet/ERC20System.sol | 11 +- .../{Metadata.sol => ERC20Metadata.sol} | 14 +- .../modules/erc721-puppet/ERC721Module.sol | 93 +++ .../modules/erc721-puppet/ERC721System.sol | 443 +++++++++++ .../modules/erc721-puppet/IERC721Mintable.sol | 37 + .../src/modules/erc721-puppet/constants.sol | 24 + .../modules/erc721-puppet/registerERC721.sol | 37 + .../erc721-puppet/tables/ERC721Metadata.sol | 734 ++++++++++++++++++ .../erc721-puppet/tables/ERC721Registry.sol | 229 ++++++ .../erc721-puppet/tables/OperatorApproval.sol | 251 ++++++ .../modules/erc721-puppet/tables/Owners.sol | 226 ++++++ .../erc721-puppet/tables/TokenApproval.sol | 226 ++++++ .../modules/erc721-puppet/tables/TokenURI.sol | 480 ++++++++++++ .../src/modules/erc721-puppet/utils.sol | 42 + .../tables/Balances.sol | 0 packages/world-modules/test/ERC20.t.sol | 6 +- 19 files changed, 2928 insertions(+), 31 deletions(-) rename packages/world-modules/src/modules/erc20-puppet/tables/{Metadata.sol => ERC20Metadata.sol} (97%) create mode 100644 packages/world-modules/src/modules/erc721-puppet/ERC721Module.sol create mode 100644 packages/world-modules/src/modules/erc721-puppet/ERC721System.sol create mode 100644 packages/world-modules/src/modules/erc721-puppet/IERC721Mintable.sol create mode 100644 packages/world-modules/src/modules/erc721-puppet/constants.sol create mode 100644 packages/world-modules/src/modules/erc721-puppet/registerERC721.sol create mode 100644 packages/world-modules/src/modules/erc721-puppet/tables/ERC721Metadata.sol create mode 100644 packages/world-modules/src/modules/erc721-puppet/tables/ERC721Registry.sol create mode 100644 packages/world-modules/src/modules/erc721-puppet/tables/OperatorApproval.sol create mode 100644 packages/world-modules/src/modules/erc721-puppet/tables/Owners.sol create mode 100644 packages/world-modules/src/modules/erc721-puppet/tables/TokenApproval.sol create mode 100644 packages/world-modules/src/modules/erc721-puppet/tables/TokenURI.sol create mode 100644 packages/world-modules/src/modules/erc721-puppet/utils.sol rename packages/world-modules/src/modules/{erc20-puppet => tokens}/tables/Balances.sol (100%) diff --git a/packages/world-modules/mud.config.ts b/packages/world-modules/mud.config.ts index b18913bee2..74a5a17553 100644 --- a/packages/world-modules/mud.config.ts +++ b/packages/world-modules/mud.config.ts @@ -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", }, @@ -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: { @@ -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: { + tokenId: "uint256", + operator: "address", + }, + valueSchema: { + approved: "bool", + }, + tableIdArgument: true, + }, + ERC721Registry: { + directory: "modules/erc721-puppet/tables", keySchema: { namespaceId: "ResourceId", }, @@ -160,6 +231,5 @@ export default mudConfig({ tableIdArgument: true, }, }, - - excludeSystems: ["UniqueEntitySystem", "PuppetFactorySystem", "ERC20System"], + excludeSystems: ["UniqueEntitySystem", "PuppetFactorySystem", "ERC20System", "ERC721System"], }); diff --git a/packages/world-modules/src/index.sol b/packages/world-modules/src/index.sol index 4805e545b5..b1d73d8c0d 100644 --- a/packages/world-modules/src/index.sol +++ b/packages/world-modules/src/index.sol @@ -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"; diff --git a/packages/world-modules/src/modules/erc20-puppet/ERC20Module.sol b/packages/world-modules/src/modules/erc20-puppet/ERC20Module.sol index e69f02d084..e51df95407 100644 --- a/packages/world-modules/src/modules/erc20-puppet/ERC20Module.sol +++ b/packages/world-modules/src/modules/erc20-puppet/ERC20Module.sol @@ -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); @@ -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); @@ -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) { @@ -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()); diff --git a/packages/world-modules/src/modules/erc20-puppet/ERC20System.sol b/packages/world-modules/src/modules/erc20-puppet/ERC20System.sol index 878f51f39b..8c5a5474e4 100644 --- a/packages/world-modules/src/modules/erc20-puppet/ERC20System.sol +++ b/packages/world-modules/src/modules/erc20-puppet/ERC20System.sol @@ -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"; @@ -28,7 +27,7 @@ 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())); } /** @@ -36,7 +35,7 @@ contract ERC20System is System, IERC20Mintable, PuppetMaster { * name. */ function symbol() public view virtual returns (string memory) { - return Metadata.getSymbol(_metadataTableId(_namespace())); + return ERC20Metadata.getSymbol(_metadataTableId(_namespace())); } /** @@ -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())); } /** diff --git a/packages/world-modules/src/modules/erc20-puppet/tables/Metadata.sol b/packages/world-modules/src/modules/erc20-puppet/tables/ERC20Metadata.sol similarity index 97% rename from packages/world-modules/src/modules/erc20-puppet/tables/Metadata.sol rename to packages/world-modules/src/modules/erc20-puppet/tables/ERC20Metadata.sol index d24ef388df..c62353a8e4 100644 --- a/packages/world-modules/src/modules/erc20-puppet/tables/Metadata.sol +++ b/packages/world-modules/src/modules/erc20-puppet/tables/ERC20Metadata.sol @@ -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. @@ -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( @@ -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( @@ -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); @@ -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); @@ -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); diff --git a/packages/world-modules/src/modules/erc721-puppet/ERC721Module.sol b/packages/world-modules/src/modules/erc721-puppet/ERC721Module.sol new file mode 100644 index 0000000000..3372547da3 --- /dev/null +++ b/packages/world-modules/src/modules/erc721-puppet/ERC721Module.sol @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.21; + +import { ResourceIds } from "@latticexyz/store/src/codegen/tables/ResourceIds.sol"; +import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; +import { Module } from "@latticexyz/world/src/Module.sol"; +import { WorldResourceIdLib } from "@latticexyz/world/src/WorldResourceId.sol"; +import { IBaseWorld } from "@latticexyz/world/src/codegen/interfaces/IBaseWorld.sol"; +import { InstalledModules } from "@latticexyz/world/src/codegen/tables/InstalledModules.sol"; + +import { Puppet } from "../puppet/Puppet.sol"; +import { createPuppet } from "../puppet/createPuppet.sol"; +import { MODULE_NAME as PUPPET_MODULE_NAME } from "../puppet/constants.sol"; +import { PuppetModule } from "../puppet/PuppetModule.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 { ERC721System } from "./ERC721System.sol"; + +import { ERC721Registry } from "./tables/ERC721Registry.sol"; +import { ERC721Metadata, ERC721MetadataData } from "./tables/Metadata.sol"; + +contract ERC20Module is Module { + error ERC20Module_InvalidNamespace(bytes14 namespace); + + function getName() public pure override returns (bytes16) { + return MODULE_NAME; + } + + /** + * Register systems and tables for a new ERC20 token in a given namespace + */ + function _registerERC20(bytes14 namespace) internal { + // Register the tables + Allowances.register(_allowancesTableId(namespace)); + Balances.register(_balancesTableId(namespace)); + Metadata.register(_metadataTableId(namespace)); + + // Register a new ERC20System + IBaseWorld(_world()).registerSystem(_erc20SystemId(namespace), new ERC20System(), true); + } + + function _installDependencies() internal { + // If the PuppetModule is not installed yet, install it + if (InstalledModules.get(PUPPET_MODULE_NAME, keccak256(new bytes(0))) == address(0)) { + IBaseWorld(_world()).installModule(new PuppetModule(), new bytes(0)); + } + } + + function install(bytes memory args) public { + // Require the module to not be installed with these args yet + if (InstalledModules.get(MODULE_NAME, keccak256(args)) != address(0)) { + revert Module_AlreadyInstalled(); + } + + // Extract args + (bytes14 namespace, MetadataData memory metadata) = abi.decode(args, (bytes14, MetadataData)); + + // Require the namespace to not be the module's namespace + if (namespace == MODULE_NAMESPACE) { + revert ERC20Module_InvalidNamespace(namespace); + } + + // Install dependencies + _installDependencies(); + + // Register the ERC20 tables and system + _registerERC20(namespace); + + // Initialize the Metadata + Metadata.set(_metadataTableId(namespace), metadata); + + // Deploy and register the ERC20 puppet. + IBaseWorld world = IBaseWorld(_world()); + ResourceId erc20SystemId = _erc20SystemId(namespace); + address puppet = createPuppet(world, erc20SystemId); + + // Transfer ownership of the namespace to the caller + ResourceId namespaceId = WorldResourceIdLib.encodeNamespace(namespace); + world.transferOwnership(namespaceId, _msgSender()); + + // Register the ERC20 in the ERC20Registry + if (!ResourceIds.getExists(ERC20_REGISTRY_TABLE_ID)) { + ERC20Registry.register(ERC20_REGISTRY_TABLE_ID); + } + ERC20Registry.set(ERC20_REGISTRY_TABLE_ID, namespaceId, puppet); + } + + function installRoot(bytes memory) public pure { + revert Module_RootInstallNotSupported(); + } +} diff --git a/packages/world-modules/src/modules/erc721-puppet/ERC721System.sol b/packages/world-modules/src/modules/erc721-puppet/ERC721System.sol new file mode 100644 index 0000000000..4ad32466e2 --- /dev/null +++ b/packages/world-modules/src/modules/erc721-puppet/ERC721System.sol @@ -0,0 +1,443 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.21; + +import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; +import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; +import { RESOURCE_TABLE } from "@latticexyz/store/src/storeResourceTypes.sol"; +import { System } from "@latticexyz/world/src/System.sol"; +import { WorldResourceIdInstance, WorldResourceIdLib } 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 { AccessControlLib } from "../../utils/AccessControlLib.sol"; +import { PuppetMaster } from "../puppet/PuppetMaster.sol"; +import { Balances } from "../tokens/tables/Balances.sol"; + +import { IERC721Mintable } from "./IERC721Mintable.sol"; + +import { ERC721Metadata } from "./tables/ERC721Metadata.sol"; +import { OperatorApproval } from "./tables/OperatorApproval.sol"; +import { Owners } from "./tables/Owners.sol"; +import { TokenApproval } from "./tables/TokenApproval.sol"; +import { TokenURI } from "./tables/TokenURI.sol"; + +import { _balancesTableId, _metadataTableId, _tokenUriTableId, _operatorApprovalTableId, _ownersTableId, _tokenApprovalTableId, _toBytes32 } from "./utils.sol"; + +contract ERC721System is System, IERC721Mintable, PuppetMaster { + using Strings for uint256; + using WorldResourceIdInstance for ResourceId; + + /** + * @dev See {IERC721-balanceOf}. + */ + function balanceOf(address owner) public view virtual returns (uint256) { + if (owner == address(0)) { + revert ERC721InvalidOwner(address(0)); + } + return Balances.get(_balancesTableId(_namespace()), owner); + } + + /** + * @dev See {IERC721-ownerOf}. + */ + function ownerOf(uint256 tokenId) public view virtual returns (address) { + return _requireOwned(tokenId); + } + + /** + * @dev See {IERC721Metadata-name}. + */ + function name() public view virtual returns (string memory) { + return ERC721Metadata.getName(_metadataTableId(_namespace())); + } + + /** + * @dev See {IERC721Metadata-symbol}. + */ + function symbol() public view virtual returns (string memory) { + return ERC721Metadata.getSymbol(_metadataTableId(_namespace())); + } + + /** + * @dev See {IERC721Metadata-tokenURI}. + */ + function tokenURI(uint256 tokenId) public view virtual returns (string memory) { + _requireOwned(tokenId); + + string memory baseURI = _baseURI(); + string memory _tokenURI = TokenURI.get(_tokenUriTableId(_namespace()), tokenId); + _tokenURI = bytes(_tokenURI).length > 0 ? _tokenURI : tokenId.toString(); + return bytes(baseURI).length > 0 ? string.concat(baseURI, _tokenURI) : _tokenURI; + } + + /** + * @dev Base URI for computing {tokenURI}. If set, the resulting URI for each + * token will be the concatenation of the `baseURI` and the `tokenId`. + */ + function _baseURI() internal view virtual returns (string memory) { + return ERC721Metadata.getBaseURI(_metadataTableId(_namespace())); + } + + /** + * @dev See {IERC721-approve}. + */ + function approve(address to, uint256 tokenId) public virtual { + _approve(to, tokenId, _msgSender()); + } + + /** + * @dev See {IERC721-getApproved}. + */ + function getApproved(uint256 tokenId) public view virtual returns (address) { + _requireOwned(tokenId); + + return _getApproved(tokenId); + } + + /** + * @dev See {IERC721-setApprovalForAll}. + */ + function setApprovalForAll(address operator, bool approved) public virtual { + _setApprovalForAll(_msgSender(), operator, approved); + } + + /** + * @dev See {IERC721-isApprovedForAll}. + */ + function isApprovedForAll(address owner, address operator) public view virtual returns (bool) { + return OperatorApproval.get(_operatorApprovalTableId(_namespace()), owner, operator); + } + + /** + * @dev See {IERC721-transferFrom}. + */ + function transferFrom(address from, address to, uint256 tokenId) public virtual { + if (to == address(0)) { + revert ERC721InvalidReceiver(address(0)); + } + // Setting an "auth" arguments enables the `_isAuthorized` check which verifies that the token exists + // (from != 0). Therefore, it is not needed to verify that the return value is not 0 here. + address previousOwner = _update(to, tokenId, _msgSender()); + if (previousOwner != from) { + revert ERC721IncorrectOwner(from, tokenId, previousOwner); + } + } + + /** + * @dev See {IERC721-safeTransferFrom}. + */ + function safeTransferFrom(address from, address to, uint256 tokenId) public { + safeTransferFrom(from, to, tokenId, ""); + } + + /** + * @dev See {IERC721-safeTransferFrom}. + */ + function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public virtual { + transferFrom(from, to, tokenId); + _checkOnERC721Received(from, to, tokenId, data); + } + + /** + * @dev Returns the owner of the `tokenId`. Does NOT revert if token doesn't exist + * + * IMPORTANT: Any overrides to this function that add ownership of tokens not tracked by the + * core ERC721 logic MUST be matched with the use of {_increaseBalance} to keep balances + * consistent with ownership. The invariant to preserve is that for any address `a` the value returned by + * `balanceOf(a)` must be equal to the number of tokens such that `_ownerOf(tokenId)` is `a`. + */ + function _ownerOf(uint256 tokenId) internal view virtual returns (address) { + return Owners.get(_ownersTableId(_namespace()), tokenId); + } + + /** + * @dev Returns the approved address for `tokenId`. Returns 0 if `tokenId` is not minted. + */ + function _getApproved(uint256 tokenId) internal view virtual returns (address) { + return TokenApproval.get(_tokenApprovalTableId(_namespace()), tokenId); + } + + /** + * @dev Returns whether `spender` is allowed to manage `owner`'s tokens, or `tokenId` in + * particular (ignoring whether it is owned by `owner`). + * + * WARNING: This function assumes that `owner` is the actual owner of `tokenId` and does not verify this + * assumption. + */ + function _isAuthorized(address owner, address spender, uint256 tokenId) internal view virtual returns (bool) { + return + spender != address(0) && + (owner == spender || isApprovedForAll(owner, spender) || _getApproved(tokenId) == spender); + } + + /** + * @dev Checks if `spender` can operate on `tokenId`, assuming the provided `owner` is the actual owner. + * Reverts if `spender` does not have approval from the provided `owner` for the given token or for all its assets + * the `spender` for the specific `tokenId`. + * + * WARNING: This function assumes that `owner` is the actual owner of `tokenId` and does not verify this + * assumption. + */ + function _checkAuthorized(address owner, address spender, uint256 tokenId) internal view virtual { + if (!_isAuthorized(owner, spender, tokenId)) { + if (owner == address(0)) { + revert ERC721NonexistentToken(tokenId); + } else { + revert ERC721InsufficientApproval(spender, tokenId); + } + } + } + + /** + * @dev Unsafe write access to the balances, used by extensions that "mint" tokens using an {ownerOf} override. + * + * NOTE: the value is limited to type(uint128).max. This protect against _balance overflow. It is unrealistic that + * a uint256 would ever overflow from increments when these increments are bounded to uint128 values. + * + * WARNING: Increasing an account's balance using this function tends to be paired with an override of the + * {_ownerOf} function to resolve the ownership of the corresponding tokens so that balances and ownership + * remain consistent with one another. + */ + function _increaseBalance(address account, uint128 value) internal virtual { + ResourceId balanceTableId = _balancesTableId(_namespace()); + unchecked { + Balances.set(balanceTableId, account, Balances.get(balanceTableId, account) + value); + } + } + + /** + * @dev Transfers `tokenId` from its current owner to `to`, or alternatively mints (or burns) if the current owner + * (or `to`) is the zero address. Returns the owner of the `tokenId` before the update. + * + * The `auth` argument is optional. If the value passed is non 0, then this function will check that + * `auth` is either the owner of the token, or approved to operate on the token (by the owner). + * + * Emits a {Transfer} event. + * + * NOTE: If overriding this function in a way that tracks balances, see also {_increaseBalance}. + */ + function _update(address to, uint256 tokenId, address auth) internal virtual returns (address) { + ResourceId balanceTableId = _balancesTableId(_namespace()); + address from = _ownerOf(tokenId); + + // Perform (optional) operator check + if (auth != address(0)) { + _checkAuthorized(from, auth, tokenId); + } + + // Execute the update + if (from != address(0)) { + // Clear approval. No need to re-authorize or emit the Approval event + _approve(address(0), tokenId, address(0), false); + + unchecked { + Balances.set(balanceTableId, from, Balances.get(balanceTableId, from) - 1); + } + } + + if (to != address(0)) { + unchecked { + Balances.set(balanceTableId, from, Balances.get(balanceTableId, from) + 1); + } + } + + Owners.set(_ownersTableId(_namespace()), tokenId, to); + + emit Transfer(from, to, tokenId); + + return from; + } + + /** + * @dev Mints `tokenId` and transfers it to `to`. + * + * WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible + * + * Requirements: + * + * - `tokenId` must not exist. + * - `to` cannot be the zero address. + * + * Emits a {Transfer} event. + */ + function _mint(address to, uint256 tokenId) internal { + if (to == address(0)) { + revert ERC721InvalidReceiver(address(0)); + } + address previousOwner = _update(to, tokenId, address(0)); + if (previousOwner != address(0)) { + revert ERC721InvalidSender(address(0)); + } + } + + /** + * @dev Mints `tokenId`, transfers it to `to` and checks for `to` acceptance. + * + * Requirements: + * + * - `tokenId` must not exist. + * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. + * + * Emits a {Transfer} event. + */ + function _safeMint(address to, uint256 tokenId) internal { + _safeMint(to, tokenId, ""); + } + + /** + * @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is + * forwarded in {IERC721Receiver-onERC721Received} to contract recipients. + */ + function _safeMint(address to, uint256 tokenId, bytes memory data) internal virtual { + _mint(to, tokenId); + _checkOnERC721Received(address(0), to, tokenId, data); + } + + /** + * @dev Destroys `tokenId`. + * The approval is cleared when the token is burned. + * This is an internal function that does not check if the sender is authorized to operate on the token. + * + * Requirements: + * + * - `tokenId` must exist. + * + * Emits a {Transfer} event. + */ + function _burn(uint256 tokenId) internal { + address previousOwner = _update(address(0), tokenId, address(0)); + if (previousOwner == address(0)) { + revert ERC721NonexistentToken(tokenId); + } + } + + /** + * @dev Transfers `tokenId` from `from` to `to`. + * As opposed to {transferFrom}, this imposes no restrictions on msg.sender. + * + * Requirements: + * + * - `to` cannot be the zero address. + * - `tokenId` token must be owned by `from`. + * + * Emits a {Transfer} event. + */ + function _transfer(address from, address to, uint256 tokenId) internal { + if (to == address(0)) { + revert ERC721InvalidReceiver(address(0)); + } + address previousOwner = _update(to, tokenId, address(0)); + if (previousOwner == address(0)) { + revert ERC721NonexistentToken(tokenId); + } else if (previousOwner != from) { + revert ERC721IncorrectOwner(from, tokenId, previousOwner); + } + } + + /** + * @dev Safely transfers `tokenId` token from `from` to `to`, checking that contract recipients + * are aware of the ERC721 standard to prevent tokens from being forever locked. + * + * `data` is additional data, it has no specified format and it is sent in call to `to`. + * + * This internal function is like {safeTransferFrom} in the sense that it invokes + * {IERC721Receiver-onERC721Received} on the receiver, and can be used to e.g. + * implement alternative mechanisms to perform token transfer, such as signature-based. + * + * Requirements: + * + * - `tokenId` token must exist and be owned by `from`. + * - `to` cannot be the zero address. + * - `from` cannot be the zero address. + * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. + * + * Emits a {Transfer} event. + */ + function _safeTransfer(address from, address to, uint256 tokenId) internal { + _safeTransfer(from, to, tokenId, ""); + } + + /** + * @dev Same as {xref-ERC721-_safeTransfer-address-address-uint256-}[`_safeTransfer`], with an additional `data` parameter which is + * forwarded in {IERC721Receiver-onERC721Received} to contract recipients. + */ + function _safeTransfer(address from, address to, uint256 tokenId, bytes memory data) internal virtual { + _transfer(from, to, tokenId); + _checkOnERC721Received(from, to, tokenId, data); + } + + /** + * @dev Approve `to` to operate on `tokenId` + * + * The `auth` argument is optional. If the value passed is non 0, then this function will check that `auth` is + * either the owner of the token, or approved to operate on all tokens held by this owner. + * + * Emits an {Approval} event. + * + * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument. + */ + function _approve(address to, uint256 tokenId, address auth) internal { + _approve(to, tokenId, auth, true); + } + + /** + * @dev Variant of `_approve` with an optional flag to enable or disable the {Approval} event. The event is not + * emitted in the context of transfers. + */ + function _approve(address to, uint256 tokenId, address auth, bool emitEvent) internal virtual { + // Avoid reading the owner unless necessary + if (emitEvent || auth != address(0)) { + address owner = _requireOwned(tokenId); + + // We do not use _isAuthorized because single-token approvals should not be able to call approve + if (auth != address(0) && owner != auth && !isApprovedForAll(owner, auth)) { + revert ERC721InvalidApprover(auth); + } + + if (emitEvent) { + emit Approval(owner, to, tokenId); + } + } + + TokenApproval.set(_tokenApprovalTableId(_namespace()), tokenId, to); + } + + /** + * @dev Approve `operator` to operate on all of `owner` tokens + * + * Requirements: + * - operator can't be the address zero. + * + * Emits an {ApprovalForAll} event. + */ + function _setApprovalForAll(address owner, address operator, bool approved) internal virtual { + if (operator == address(0)) { + revert ERC721InvalidOperator(operator); + } + OperatorApproval.set(_operatorApprovalTableId(_namespace()), owner, operator, approved); + emit ApprovalForAll(owner, operator, approved); + } + + /** + * @dev Reverts if the `tokenId` doesn't have a current owner (it hasn't been minted, or it has been burned). + * Returns the owner. + * + * Overrides to ownership logic should be done to {_ownerOf}. + */ + function _requireOwned(uint256 tokenId) internal view returns (address) { + address owner = _ownerOf(tokenId); + if (owner == address(0)) { + revert ERC721NonexistentToken(tokenId); + } + return owner; + } + + function _namespace() internal view returns (bytes14 namespace) { + ResourceId systemId = SystemRegistry.get(address(this)); + return systemId.getNamespace(); + } + + function _requireOwner() internal view { + AccessControlLib.requireOwner(SystemRegistry.get(address(this)), _msgSender()); + } +} diff --git a/packages/world-modules/src/modules/erc721-puppet/IERC721Mintable.sol b/packages/world-modules/src/modules/erc721-puppet/IERC721Mintable.sol new file mode 100644 index 0000000000..88a066b943 --- /dev/null +++ b/packages/world-modules/src/modules/erc721-puppet/IERC721Mintable.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol) + +pragma solidity >=0.8.21; + +import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import { IERC721Metadata } from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol"; +import { IERC721Errors } from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol"; + +/** + * @dev Extending the ERC721 standard with permissioned mint and burn functions. + */ +interface IERC721Mintable is IERC721, IERC721Metadata, IERC721Errors { + /** + * @dev Mints `tokenId` and transfers it to `to`. + * + * Requirements: + * + * - `tokenId` must not exist. + * - `to` cannot be the zero address. + * + * Emits a {Transfer} event. + */ + function mint(address to, uint256 tokenId) external; + + /** + * @dev Destroys `tokenId`. + * The approval is cleared when the token is burned. + * + * Requirements: + * + * - `tokenId` must exist. + * + * Emits a {Transfer} event. + */ + function burn(uint256 tokenId) external; +} diff --git a/packages/world-modules/src/modules/erc721-puppet/constants.sol b/packages/world-modules/src/modules/erc721-puppet/constants.sol new file mode 100644 index 0000000000..8967ed7e09 --- /dev/null +++ b/packages/world-modules/src/modules/erc721-puppet/constants.sol @@ -0,0 +1,24 @@ +// 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, RESOURCE_NAMESPACE } from "@latticexyz/world/src/worldResourceTypes.sol"; + +bytes16 constant MODULE_NAME = "erc721-puppet"; +bytes14 constant MODULE_NAMESPACE = "erc721-puppet"; +ResourceId constant MODULE_NAMESPACE_ID = ResourceId.wrap( + bytes32(abi.encodePacked(RESOURCE_NAMESPACE, MODULE_NAMESPACE)) +); + +bytes16 constant TOKEN_URI_NAME = "TokenURI"; +bytes16 constant BALANCES_NAME = "Balances"; +bytes16 constant METADATA_NAME = "Metadata"; +bytes16 constant OPERATOR_APPROVAL_NAME = "OperatorApproval"; +bytes16 constant TOKEN_APPROVAL_NAME = "TokenApproval"; +bytes16 constant OWNERS_NAME = "Owners"; + +bytes16 constant ERC721_SYSTEM_NAME = "ERC721System"; + +ResourceId constant ERC721_REGISTRY_TABLE_ID = ResourceId.wrap( + bytes32(abi.encodePacked(RESOURCE_TABLE, MODULE_NAMESPACE, bytes16("ERC721Registry"))) +); diff --git a/packages/world-modules/src/modules/erc721-puppet/registerERC721.sol b/packages/world-modules/src/modules/erc721-puppet/registerERC721.sol new file mode 100644 index 0000000000..40b0d34874 --- /dev/null +++ b/packages/world-modules/src/modules/erc721-puppet/registerERC721.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.21; + +import { IBaseWorld } from "@latticexyz/world/src/codegen/interfaces/IBaseWorld.sol"; +import { NamespaceOwner } from "@latticexyz/world/src/codegen/tables/NamespaceOwner.sol"; +import { WorldResourceIdLib } from "@latticexyz/world/src/WorldResourceId.sol"; + +import { SystemSwitch } from "../../utils/SystemSwitch.sol"; + +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 { ERC20Registry } from "./tables/ERC20Registry.sol"; + +/** + * @notice Register a new ERC20 token with the given metadata in a given namespace + * @dev This function must be called within a Store context (i.e. using StoreSwitch.setStoreAddress()) + */ +function registerERC20( + IBaseWorld world, + bytes14 namespace, + MetadataData memory metadata +) returns (IERC20Mintable token) { + // Get the ERC20 module + ERC20Module erc20Module = ERC20Module(NamespaceOwner.get(MODULE_NAMESPACE_ID)); + if (address(erc20Module) == address(0)) { + erc20Module = new ERC20Module(); + } + + // Install the ERC20 module with the provided args + world.installModule(erc20Module, abi.encode(namespace, metadata)); + + // Return the newly created ERC20 token + token = IERC20Mintable(ERC20Registry.get(ERC20_REGISTRY_TABLE_ID, WorldResourceIdLib.encodeNamespace(namespace))); +} diff --git a/packages/world-modules/src/modules/erc721-puppet/tables/ERC721Metadata.sol b/packages/world-modules/src/modules/erc721-puppet/tables/ERC721Metadata.sol new file mode 100644 index 0000000000..3586fa252f --- /dev/null +++ b/packages/world-modules/src/modules/erc721-puppet/tables/ERC721Metadata.sol @@ -0,0 +1,734 @@ +// 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"; + +FieldLayout constant _fieldLayout = FieldLayout.wrap( + 0x0000000300000000000000000000000000000000000000000000000000000000 +); + +struct ERC721MetadataData { + string name; + string symbol; + string baseURI; +} + +library ERC721Metadata { + /** + * @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[](0); + + 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[](3); + _valueSchema[0] = SchemaType.STRING; + _valueSchema[1] = SchemaType.STRING; + _valueSchema[2] = SchemaType.STRING; + + 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[](0); + } + + /** + * @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[](3); + fieldNames[0] = "name"; + fieldNames[1] = "symbol"; + fieldNames[2] = "baseURI"; + } + + /** + * @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 name. + */ + function getName(ResourceId _tableId) internal view returns (string memory name) { + bytes32[] memory _keyTuple = new bytes32[](0); + + bytes memory _blob = StoreSwitch.getDynamicField(_tableId, _keyTuple, 0); + return (string(_blob)); + } + + /** + * @notice Get name. + */ + function _getName(ResourceId _tableId) internal view returns (string memory name) { + bytes32[] memory _keyTuple = new bytes32[](0); + + bytes memory _blob = StoreCore.getDynamicField(_tableId, _keyTuple, 0); + return (string(_blob)); + } + + /** + * @notice Set name. + */ + function setName(ResourceId _tableId, string memory name) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreSwitch.setDynamicField(_tableId, _keyTuple, 0, bytes((name))); + } + + /** + * @notice Set name. + */ + function _setName(ResourceId _tableId, string memory name) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreCore.setDynamicField(_tableId, _keyTuple, 0, bytes((name))); + } + + /** + * @notice Get the length of name. + */ + function lengthName(ResourceId _tableId) internal view returns (uint256) { + bytes32[] memory _keyTuple = new bytes32[](0); + + uint256 _byteLength = StoreSwitch.getDynamicFieldLength(_tableId, _keyTuple, 0); + unchecked { + return _byteLength / 1; + } + } + + /** + * @notice Get the length of name. + */ + function _lengthName(ResourceId _tableId) internal view returns (uint256) { + bytes32[] memory _keyTuple = new bytes32[](0); + + uint256 _byteLength = StoreCore.getDynamicFieldLength(_tableId, _keyTuple, 0); + unchecked { + return _byteLength / 1; + } + } + + /** + * @notice Get an item of name. + * @dev Reverts with Store_IndexOutOfBounds if `_index` is out of bounds for the array. + */ + function getItemName(ResourceId _tableId, uint256 _index) internal view returns (string memory) { + bytes32[] memory _keyTuple = new bytes32[](0); + + unchecked { + bytes memory _blob = StoreSwitch.getDynamicFieldSlice(_tableId, _keyTuple, 0, _index * 1, (_index + 1) * 1); + return (string(_blob)); + } + } + + /** + * @notice Get an item of name. + * @dev Reverts with Store_IndexOutOfBounds if `_index` is out of bounds for the array. + */ + function _getItemName(ResourceId _tableId, uint256 _index) internal view returns (string memory) { + bytes32[] memory _keyTuple = new bytes32[](0); + + unchecked { + bytes memory _blob = StoreCore.getDynamicFieldSlice(_tableId, _keyTuple, 0, _index * 1, (_index + 1) * 1); + return (string(_blob)); + } + } + + /** + * @notice Push a slice to name. + */ + function pushName(ResourceId _tableId, string memory _slice) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreSwitch.pushToDynamicField(_tableId, _keyTuple, 0, bytes((_slice))); + } + + /** + * @notice Push a slice to name. + */ + function _pushName(ResourceId _tableId, string memory _slice) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreCore.pushToDynamicField(_tableId, _keyTuple, 0, bytes((_slice))); + } + + /** + * @notice Pop a slice from name. + */ + function popName(ResourceId _tableId) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreSwitch.popFromDynamicField(_tableId, _keyTuple, 0, 1); + } + + /** + * @notice Pop a slice from name. + */ + function _popName(ResourceId _tableId) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreCore.popFromDynamicField(_tableId, _keyTuple, 0, 1); + } + + /** + * @notice Update a slice of name at `_index`. + */ + function updateName(ResourceId _tableId, uint256 _index, string memory _slice) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + unchecked { + bytes memory _encoded = bytes((_slice)); + StoreSwitch.spliceDynamicData(_tableId, _keyTuple, 0, uint40(_index * 1), uint40(_encoded.length), _encoded); + } + } + + /** + * @notice Update a slice of name at `_index`. + */ + function _updateName(ResourceId _tableId, uint256 _index, string memory _slice) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + unchecked { + bytes memory _encoded = bytes((_slice)); + StoreCore.spliceDynamicData(_tableId, _keyTuple, 0, uint40(_index * 1), uint40(_encoded.length), _encoded); + } + } + + /** + * @notice Get symbol. + */ + function getSymbol(ResourceId _tableId) internal view returns (string memory symbol) { + bytes32[] memory _keyTuple = new bytes32[](0); + + bytes memory _blob = StoreSwitch.getDynamicField(_tableId, _keyTuple, 1); + return (string(_blob)); + } + + /** + * @notice Get symbol. + */ + function _getSymbol(ResourceId _tableId) internal view returns (string memory symbol) { + bytes32[] memory _keyTuple = new bytes32[](0); + + bytes memory _blob = StoreCore.getDynamicField(_tableId, _keyTuple, 1); + return (string(_blob)); + } + + /** + * @notice Set symbol. + */ + function setSymbol(ResourceId _tableId, string memory symbol) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreSwitch.setDynamicField(_tableId, _keyTuple, 1, bytes((symbol))); + } + + /** + * @notice Set symbol. + */ + function _setSymbol(ResourceId _tableId, string memory symbol) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreCore.setDynamicField(_tableId, _keyTuple, 1, bytes((symbol))); + } + + /** + * @notice Get the length of symbol. + */ + function lengthSymbol(ResourceId _tableId) internal view returns (uint256) { + bytes32[] memory _keyTuple = new bytes32[](0); + + uint256 _byteLength = StoreSwitch.getDynamicFieldLength(_tableId, _keyTuple, 1); + unchecked { + return _byteLength / 1; + } + } + + /** + * @notice Get the length of symbol. + */ + function _lengthSymbol(ResourceId _tableId) internal view returns (uint256) { + bytes32[] memory _keyTuple = new bytes32[](0); + + uint256 _byteLength = StoreCore.getDynamicFieldLength(_tableId, _keyTuple, 1); + unchecked { + return _byteLength / 1; + } + } + + /** + * @notice Get an item of symbol. + * @dev Reverts with Store_IndexOutOfBounds if `_index` is out of bounds for the array. + */ + function getItemSymbol(ResourceId _tableId, uint256 _index) internal view returns (string memory) { + bytes32[] memory _keyTuple = new bytes32[](0); + + unchecked { + bytes memory _blob = StoreSwitch.getDynamicFieldSlice(_tableId, _keyTuple, 1, _index * 1, (_index + 1) * 1); + return (string(_blob)); + } + } + + /** + * @notice Get an item of symbol. + * @dev Reverts with Store_IndexOutOfBounds if `_index` is out of bounds for the array. + */ + function _getItemSymbol(ResourceId _tableId, uint256 _index) internal view returns (string memory) { + bytes32[] memory _keyTuple = new bytes32[](0); + + unchecked { + bytes memory _blob = StoreCore.getDynamicFieldSlice(_tableId, _keyTuple, 1, _index * 1, (_index + 1) * 1); + return (string(_blob)); + } + } + + /** + * @notice Push a slice to symbol. + */ + function pushSymbol(ResourceId _tableId, string memory _slice) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreSwitch.pushToDynamicField(_tableId, _keyTuple, 1, bytes((_slice))); + } + + /** + * @notice Push a slice to symbol. + */ + function _pushSymbol(ResourceId _tableId, string memory _slice) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreCore.pushToDynamicField(_tableId, _keyTuple, 1, bytes((_slice))); + } + + /** + * @notice Pop a slice from symbol. + */ + function popSymbol(ResourceId _tableId) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreSwitch.popFromDynamicField(_tableId, _keyTuple, 1, 1); + } + + /** + * @notice Pop a slice from symbol. + */ + function _popSymbol(ResourceId _tableId) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreCore.popFromDynamicField(_tableId, _keyTuple, 1, 1); + } + + /** + * @notice Update a slice of symbol at `_index`. + */ + function updateSymbol(ResourceId _tableId, uint256 _index, string memory _slice) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + unchecked { + bytes memory _encoded = bytes((_slice)); + StoreSwitch.spliceDynamicData(_tableId, _keyTuple, 1, uint40(_index * 1), uint40(_encoded.length), _encoded); + } + } + + /** + * @notice Update a slice of symbol at `_index`. + */ + function _updateSymbol(ResourceId _tableId, uint256 _index, string memory _slice) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + unchecked { + bytes memory _encoded = bytes((_slice)); + StoreCore.spliceDynamicData(_tableId, _keyTuple, 1, uint40(_index * 1), uint40(_encoded.length), _encoded); + } + } + + /** + * @notice Get baseURI. + */ + function getBaseURI(ResourceId _tableId) internal view returns (string memory baseURI) { + bytes32[] memory _keyTuple = new bytes32[](0); + + bytes memory _blob = StoreSwitch.getDynamicField(_tableId, _keyTuple, 2); + return (string(_blob)); + } + + /** + * @notice Get baseURI. + */ + function _getBaseURI(ResourceId _tableId) internal view returns (string memory baseURI) { + bytes32[] memory _keyTuple = new bytes32[](0); + + bytes memory _blob = StoreCore.getDynamicField(_tableId, _keyTuple, 2); + return (string(_blob)); + } + + /** + * @notice Set baseURI. + */ + function setBaseURI(ResourceId _tableId, string memory baseURI) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreSwitch.setDynamicField(_tableId, _keyTuple, 2, bytes((baseURI))); + } + + /** + * @notice Set baseURI. + */ + function _setBaseURI(ResourceId _tableId, string memory baseURI) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreCore.setDynamicField(_tableId, _keyTuple, 2, bytes((baseURI))); + } + + /** + * @notice Get the length of baseURI. + */ + function lengthBaseURI(ResourceId _tableId) internal view returns (uint256) { + bytes32[] memory _keyTuple = new bytes32[](0); + + uint256 _byteLength = StoreSwitch.getDynamicFieldLength(_tableId, _keyTuple, 2); + unchecked { + return _byteLength / 1; + } + } + + /** + * @notice Get the length of baseURI. + */ + function _lengthBaseURI(ResourceId _tableId) internal view returns (uint256) { + bytes32[] memory _keyTuple = new bytes32[](0); + + uint256 _byteLength = StoreCore.getDynamicFieldLength(_tableId, _keyTuple, 2); + unchecked { + return _byteLength / 1; + } + } + + /** + * @notice Get an item of baseURI. + * @dev Reverts with Store_IndexOutOfBounds if `_index` is out of bounds for the array. + */ + function getItemBaseURI(ResourceId _tableId, uint256 _index) internal view returns (string memory) { + bytes32[] memory _keyTuple = new bytes32[](0); + + unchecked { + bytes memory _blob = StoreSwitch.getDynamicFieldSlice(_tableId, _keyTuple, 2, _index * 1, (_index + 1) * 1); + return (string(_blob)); + } + } + + /** + * @notice Get an item of baseURI. + * @dev Reverts with Store_IndexOutOfBounds if `_index` is out of bounds for the array. + */ + function _getItemBaseURI(ResourceId _tableId, uint256 _index) internal view returns (string memory) { + bytes32[] memory _keyTuple = new bytes32[](0); + + unchecked { + bytes memory _blob = StoreCore.getDynamicFieldSlice(_tableId, _keyTuple, 2, _index * 1, (_index + 1) * 1); + return (string(_blob)); + } + } + + /** + * @notice Push a slice to baseURI. + */ + function pushBaseURI(ResourceId _tableId, string memory _slice) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreSwitch.pushToDynamicField(_tableId, _keyTuple, 2, bytes((_slice))); + } + + /** + * @notice Push a slice to baseURI. + */ + function _pushBaseURI(ResourceId _tableId, string memory _slice) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreCore.pushToDynamicField(_tableId, _keyTuple, 2, bytes((_slice))); + } + + /** + * @notice Pop a slice from baseURI. + */ + function popBaseURI(ResourceId _tableId) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreSwitch.popFromDynamicField(_tableId, _keyTuple, 2, 1); + } + + /** + * @notice Pop a slice from baseURI. + */ + function _popBaseURI(ResourceId _tableId) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreCore.popFromDynamicField(_tableId, _keyTuple, 2, 1); + } + + /** + * @notice Update a slice of baseURI at `_index`. + */ + function updateBaseURI(ResourceId _tableId, uint256 _index, string memory _slice) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + unchecked { + bytes memory _encoded = bytes((_slice)); + StoreSwitch.spliceDynamicData(_tableId, _keyTuple, 2, uint40(_index * 1), uint40(_encoded.length), _encoded); + } + } + + /** + * @notice Update a slice of baseURI at `_index`. + */ + function _updateBaseURI(ResourceId _tableId, uint256 _index, string memory _slice) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + unchecked { + bytes memory _encoded = bytes((_slice)); + StoreCore.spliceDynamicData(_tableId, _keyTuple, 2, uint40(_index * 1), uint40(_encoded.length), _encoded); + } + } + + /** + * @notice Get the full data. + */ + function get(ResourceId _tableId) internal view returns (ERC721MetadataData memory _table) { + bytes32[] memory _keyTuple = new bytes32[](0); + + (bytes memory _staticData, PackedCounter _encodedLengths, bytes memory _dynamicData) = StoreSwitch.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); + } + + /** + * @notice Get the full data. + */ + function _get(ResourceId _tableId) internal view returns (ERC721MetadataData memory _table) { + bytes32[] memory _keyTuple = new bytes32[](0); + + (bytes memory _staticData, PackedCounter _encodedLengths, bytes memory _dynamicData) = StoreCore.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); + } + + /** + * @notice Set the full data using individual values. + */ + function set(ResourceId _tableId, string memory name, string memory symbol, string memory baseURI) internal { + bytes memory _staticData; + PackedCounter _encodedLengths = encodeLengths(name, symbol, baseURI); + bytes memory _dynamicData = encodeDynamic(name, symbol, baseURI); + + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreSwitch.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData); + } + + /** + * @notice Set the full data using individual values. + */ + function _set(ResourceId _tableId, string memory name, string memory symbol, string memory baseURI) internal { + bytes memory _staticData; + PackedCounter _encodedLengths = encodeLengths(name, symbol, baseURI); + bytes memory _dynamicData = encodeDynamic(name, symbol, baseURI); + + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreCore.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, _fieldLayout); + } + + /** + * @notice Set the full data using the data struct. + */ + function set(ResourceId _tableId, ERC721MetadataData memory _table) internal { + bytes memory _staticData; + PackedCounter _encodedLengths = encodeLengths(_table.name, _table.symbol, _table.baseURI); + bytes memory _dynamicData = encodeDynamic(_table.name, _table.symbol, _table.baseURI); + + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreSwitch.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData); + } + + /** + * @notice Set the full data using the data struct. + */ + function _set(ResourceId _tableId, ERC721MetadataData memory _table) internal { + bytes memory _staticData; + PackedCounter _encodedLengths = encodeLengths(_table.name, _table.symbol, _table.baseURI); + bytes memory _dynamicData = encodeDynamic(_table.name, _table.symbol, _table.baseURI); + + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreCore.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, _fieldLayout); + } + + /** + * @notice Decode the tightly packed blob of dynamic data using the encoded lengths. + */ + function decodeDynamic( + PackedCounter _encodedLengths, + bytes memory _blob + ) internal pure returns (string memory name, string memory symbol, string memory baseURI) { + uint256 _start; + uint256 _end; + unchecked { + _end = _encodedLengths.atIndex(0); + } + name = (string(SliceLib.getSubslice(_blob, _start, _end).toBytes())); + + _start = _end; + unchecked { + _end += _encodedLengths.atIndex(1); + } + symbol = (string(SliceLib.getSubslice(_blob, _start, _end).toBytes())); + + _start = _end; + unchecked { + _end += _encodedLengths.atIndex(2); + } + baseURI = (string(SliceLib.getSubslice(_blob, _start, _end).toBytes())); + } + + /** + * @notice Decode the tightly packed blobs using this table's field layout. + * + * @param _encodedLengths Encoded lengths of dynamic fields. + * @param _dynamicData Tightly packed dynamic fields. + */ + function decode( + bytes memory, + PackedCounter _encodedLengths, + bytes memory _dynamicData + ) internal pure returns (ERC721MetadataData memory _table) { + (_table.name, _table.symbol, _table.baseURI) = decodeDynamic(_encodedLengths, _dynamicData); + } + + /** + * @notice Delete all data for given keys. + */ + function deleteRecord(ResourceId _tableId) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreSwitch.deleteRecord(_tableId, _keyTuple); + } + + /** + * @notice Delete all data for given keys. + */ + function _deleteRecord(ResourceId _tableId) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreCore.deleteRecord(_tableId, _keyTuple, _fieldLayout); + } + + /** + * @notice Tightly pack dynamic data lengths using this table's schema. + * @return _encodedLengths The lengths of the dynamic fields (packed into a single bytes32 value). + */ + function encodeLengths( + string memory name, + string memory symbol, + string memory baseURI + ) internal pure returns (PackedCounter _encodedLengths) { + // Lengths are effectively checked during copy by 2**40 bytes exceeding gas limits + unchecked { + _encodedLengths = PackedCounterLib.pack(bytes(name).length, bytes(symbol).length, bytes(baseURI).length); + } + } + + /** + * @notice Tightly pack dynamic (variable length) data using this table's schema. + * @return The dynamic data, encoded into a sequence of bytes. + */ + function encodeDynamic( + string memory name, + string memory symbol, + string memory baseURI + ) internal pure returns (bytes memory) { + return abi.encodePacked(bytes((name)), bytes((symbol)), bytes((baseURI))); + } + + /** + * @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( + string memory name, + string memory symbol, + string memory baseURI + ) internal pure returns (bytes memory, PackedCounter, bytes memory) { + bytes memory _staticData; + PackedCounter _encodedLengths = encodeLengths(name, symbol, baseURI); + bytes memory _dynamicData = encodeDynamic(name, symbol, baseURI); + + return (_staticData, _encodedLengths, _dynamicData); + } + + /** + * @notice Encode keys as a bytes32 array using this table's field layout. + */ + function encodeKeyTuple() internal pure returns (bytes32[] memory) { + bytes32[] memory _keyTuple = new bytes32[](0); + + return _keyTuple; + } +} diff --git a/packages/world-modules/src/modules/erc721-puppet/tables/ERC721Registry.sol b/packages/world-modules/src/modules/erc721-puppet/tables/ERC721Registry.sol new file mode 100644 index 0000000000..61afb8017a --- /dev/null +++ b/packages/world-modules/src/modules/erc721-puppet/tables/ERC721Registry.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 ERC721Registry { + /** + * @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] = "namespaceId"; + } + + /** + * @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] = "erc20Address"; + } + + /** + * @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 erc20Address. + */ + function getErc20Address(ResourceId _tableId, ResourceId namespaceId) internal view returns (address erc20Address) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = ResourceId.unwrap(namespaceId); + + bytes32 _blob = StoreSwitch.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (address(bytes20(_blob))); + } + + /** + * @notice Get erc20Address. + */ + function _getErc20Address(ResourceId _tableId, ResourceId namespaceId) internal view returns (address erc20Address) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = ResourceId.unwrap(namespaceId); + + bytes32 _blob = StoreCore.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (address(bytes20(_blob))); + } + + /** + * @notice Get erc20Address. + */ + function get(ResourceId _tableId, ResourceId namespaceId) internal view returns (address erc20Address) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = ResourceId.unwrap(namespaceId); + + bytes32 _blob = StoreSwitch.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (address(bytes20(_blob))); + } + + /** + * @notice Get erc20Address. + */ + function _get(ResourceId _tableId, ResourceId namespaceId) internal view returns (address erc20Address) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = ResourceId.unwrap(namespaceId); + + bytes32 _blob = StoreCore.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (address(bytes20(_blob))); + } + + /** + * @notice Set erc20Address. + */ + function setErc20Address(ResourceId _tableId, ResourceId namespaceId, address erc20Address) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = ResourceId.unwrap(namespaceId); + + StoreSwitch.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((erc20Address)), _fieldLayout); + } + + /** + * @notice Set erc20Address. + */ + function _setErc20Address(ResourceId _tableId, ResourceId namespaceId, address erc20Address) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = ResourceId.unwrap(namespaceId); + + StoreCore.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((erc20Address)), _fieldLayout); + } + + /** + * @notice Set erc20Address. + */ + function set(ResourceId _tableId, ResourceId namespaceId, address erc20Address) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = ResourceId.unwrap(namespaceId); + + StoreSwitch.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((erc20Address)), _fieldLayout); + } + + /** + * @notice Set erc20Address. + */ + function _set(ResourceId _tableId, ResourceId namespaceId, address erc20Address) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = ResourceId.unwrap(namespaceId); + + StoreCore.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((erc20Address)), _fieldLayout); + } + + /** + * @notice Delete all data for given keys. + */ + function deleteRecord(ResourceId _tableId, ResourceId namespaceId) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = ResourceId.unwrap(namespaceId); + + StoreSwitch.deleteRecord(_tableId, _keyTuple); + } + + /** + * @notice Delete all data for given keys. + */ + function _deleteRecord(ResourceId _tableId, ResourceId namespaceId) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = ResourceId.unwrap(namespaceId); + + 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 erc20Address) internal pure returns (bytes memory) { + return abi.encodePacked(erc20Address); + } + + /** + * @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 erc20Address) internal pure returns (bytes memory, PackedCounter, bytes memory) { + bytes memory _staticData = encodeStatic(erc20Address); + + 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 namespaceId) internal pure returns (bytes32[] memory) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = ResourceId.unwrap(namespaceId); + + return _keyTuple; + } +} diff --git a/packages/world-modules/src/modules/erc721-puppet/tables/OperatorApproval.sol b/packages/world-modules/src/modules/erc721-puppet/tables/OperatorApproval.sol new file mode 100644 index 0000000000..7d52c0cc15 --- /dev/null +++ b/packages/world-modules/src/modules/erc721-puppet/tables/OperatorApproval.sol @@ -0,0 +1,251 @@ +// 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"; + +FieldLayout constant _fieldLayout = FieldLayout.wrap( + 0x0001010001000000000000000000000000000000000000000000000000000000 +); + +library OperatorApproval { + /** + * @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[](2); + _keySchema[0] = SchemaType.UINT256; + _keySchema[1] = SchemaType.ADDRESS; + + 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.BOOL; + + 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[](2); + keyNames[0] = "tokenId"; + keyNames[1] = "operator"; + } + + /** + * @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] = "approved"; + } + + /** + * @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 approved. + */ + function getApproved(ResourceId _tableId, uint256 tokenId, address operator) internal view returns (bool approved) { + bytes32[] memory _keyTuple = new bytes32[](2); + _keyTuple[0] = bytes32(uint256(tokenId)); + _keyTuple[1] = bytes32(uint256(uint160(operator))); + + bytes32 _blob = StoreSwitch.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (_toBool(uint8(bytes1(_blob)))); + } + + /** + * @notice Get approved. + */ + function _getApproved(ResourceId _tableId, uint256 tokenId, address operator) internal view returns (bool approved) { + bytes32[] memory _keyTuple = new bytes32[](2); + _keyTuple[0] = bytes32(uint256(tokenId)); + _keyTuple[1] = bytes32(uint256(uint160(operator))); + + bytes32 _blob = StoreCore.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (_toBool(uint8(bytes1(_blob)))); + } + + /** + * @notice Get approved. + */ + function get(ResourceId _tableId, uint256 tokenId, address operator) internal view returns (bool approved) { + bytes32[] memory _keyTuple = new bytes32[](2); + _keyTuple[0] = bytes32(uint256(tokenId)); + _keyTuple[1] = bytes32(uint256(uint160(operator))); + + bytes32 _blob = StoreSwitch.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (_toBool(uint8(bytes1(_blob)))); + } + + /** + * @notice Get approved. + */ + function _get(ResourceId _tableId, uint256 tokenId, address operator) internal view returns (bool approved) { + bytes32[] memory _keyTuple = new bytes32[](2); + _keyTuple[0] = bytes32(uint256(tokenId)); + _keyTuple[1] = bytes32(uint256(uint160(operator))); + + bytes32 _blob = StoreCore.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (_toBool(uint8(bytes1(_blob)))); + } + + /** + * @notice Set approved. + */ + function setApproved(ResourceId _tableId, uint256 tokenId, address operator, bool approved) internal { + bytes32[] memory _keyTuple = new bytes32[](2); + _keyTuple[0] = bytes32(uint256(tokenId)); + _keyTuple[1] = bytes32(uint256(uint160(operator))); + + StoreSwitch.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((approved)), _fieldLayout); + } + + /** + * @notice Set approved. + */ + function _setApproved(ResourceId _tableId, uint256 tokenId, address operator, bool approved) internal { + bytes32[] memory _keyTuple = new bytes32[](2); + _keyTuple[0] = bytes32(uint256(tokenId)); + _keyTuple[1] = bytes32(uint256(uint160(operator))); + + StoreCore.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((approved)), _fieldLayout); + } + + /** + * @notice Set approved. + */ + function set(ResourceId _tableId, uint256 tokenId, address operator, bool approved) internal { + bytes32[] memory _keyTuple = new bytes32[](2); + _keyTuple[0] = bytes32(uint256(tokenId)); + _keyTuple[1] = bytes32(uint256(uint160(operator))); + + StoreSwitch.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((approved)), _fieldLayout); + } + + /** + * @notice Set approved. + */ + function _set(ResourceId _tableId, uint256 tokenId, address operator, bool approved) internal { + bytes32[] memory _keyTuple = new bytes32[](2); + _keyTuple[0] = bytes32(uint256(tokenId)); + _keyTuple[1] = bytes32(uint256(uint160(operator))); + + StoreCore.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((approved)), _fieldLayout); + } + + /** + * @notice Delete all data for given keys. + */ + function deleteRecord(ResourceId _tableId, uint256 tokenId, address operator) internal { + bytes32[] memory _keyTuple = new bytes32[](2); + _keyTuple[0] = bytes32(uint256(tokenId)); + _keyTuple[1] = bytes32(uint256(uint160(operator))); + + StoreSwitch.deleteRecord(_tableId, _keyTuple); + } + + /** + * @notice Delete all data for given keys. + */ + function _deleteRecord(ResourceId _tableId, uint256 tokenId, address operator) internal { + bytes32[] memory _keyTuple = new bytes32[](2); + _keyTuple[0] = bytes32(uint256(tokenId)); + _keyTuple[1] = bytes32(uint256(uint160(operator))); + + 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(bool approved) internal pure returns (bytes memory) { + return abi.encodePacked(approved); + } + + /** + * @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(bool approved) internal pure returns (bytes memory, PackedCounter, bytes memory) { + bytes memory _staticData = encodeStatic(approved); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; + + return (_staticData, _encodedLengths, _dynamicData); + } + + /** + * @notice Encode keys as a bytes32 array using this table's field layout. + */ + function encodeKeyTuple(uint256 tokenId, address operator) internal pure returns (bytes32[] memory) { + bytes32[] memory _keyTuple = new bytes32[](2); + _keyTuple[0] = bytes32(uint256(tokenId)); + _keyTuple[1] = bytes32(uint256(uint160(operator))); + + return _keyTuple; + } +} + +/** + * @notice Cast a value to a bool. + * @dev Boolean values are encoded as uint8 (1 = true, 0 = false), but Solidity doesn't allow casting between uint8 and bool. + * @param value The uint8 value to convert. + * @return result The boolean value. + */ +function _toBool(uint8 value) pure returns (bool result) { + assembly { + result := value + } +} diff --git a/packages/world-modules/src/modules/erc721-puppet/tables/Owners.sol b/packages/world-modules/src/modules/erc721-puppet/tables/Owners.sol new file mode 100644 index 0000000000..139d8e092a --- /dev/null +++ b/packages/world-modules/src/modules/erc721-puppet/tables/Owners.sol @@ -0,0 +1,226 @@ +// 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"; + +FieldLayout constant _fieldLayout = FieldLayout.wrap( + 0x0014010014000000000000000000000000000000000000000000000000000000 +); + +library Owners { + /** + * @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.UINT256; + + 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] = "tokenId"; + } + + /** + * @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] = "owner"; + } + + /** + * @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 owner. + */ + function getOwner(ResourceId _tableId, uint256 tokenId) internal view returns (address owner) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(tokenId)); + + bytes32 _blob = StoreSwitch.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (address(bytes20(_blob))); + } + + /** + * @notice Get owner. + */ + function _getOwner(ResourceId _tableId, uint256 tokenId) internal view returns (address owner) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(tokenId)); + + bytes32 _blob = StoreCore.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (address(bytes20(_blob))); + } + + /** + * @notice Get owner. + */ + function get(ResourceId _tableId, uint256 tokenId) internal view returns (address owner) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(tokenId)); + + bytes32 _blob = StoreSwitch.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (address(bytes20(_blob))); + } + + /** + * @notice Get owner. + */ + function _get(ResourceId _tableId, uint256 tokenId) internal view returns (address owner) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(tokenId)); + + bytes32 _blob = StoreCore.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (address(bytes20(_blob))); + } + + /** + * @notice Set owner. + */ + function setOwner(ResourceId _tableId, uint256 tokenId, address owner) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(tokenId)); + + StoreSwitch.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((owner)), _fieldLayout); + } + + /** + * @notice Set owner. + */ + function _setOwner(ResourceId _tableId, uint256 tokenId, address owner) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(tokenId)); + + StoreCore.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((owner)), _fieldLayout); + } + + /** + * @notice Set owner. + */ + function set(ResourceId _tableId, uint256 tokenId, address owner) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(tokenId)); + + StoreSwitch.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((owner)), _fieldLayout); + } + + /** + * @notice Set owner. + */ + function _set(ResourceId _tableId, uint256 tokenId, address owner) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(tokenId)); + + StoreCore.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((owner)), _fieldLayout); + } + + /** + * @notice Delete all data for given keys. + */ + function deleteRecord(ResourceId _tableId, uint256 tokenId) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(tokenId)); + + StoreSwitch.deleteRecord(_tableId, _keyTuple); + } + + /** + * @notice Delete all data for given keys. + */ + function _deleteRecord(ResourceId _tableId, uint256 tokenId) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(tokenId)); + + 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 owner) internal pure returns (bytes memory) { + return abi.encodePacked(owner); + } + + /** + * @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 owner) internal pure returns (bytes memory, PackedCounter, bytes memory) { + bytes memory _staticData = encodeStatic(owner); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; + + return (_staticData, _encodedLengths, _dynamicData); + } + + /** + * @notice Encode keys as a bytes32 array using this table's field layout. + */ + function encodeKeyTuple(uint256 tokenId) internal pure returns (bytes32[] memory) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(tokenId)); + + return _keyTuple; + } +} diff --git a/packages/world-modules/src/modules/erc721-puppet/tables/TokenApproval.sol b/packages/world-modules/src/modules/erc721-puppet/tables/TokenApproval.sol new file mode 100644 index 0000000000..908ade8d32 --- /dev/null +++ b/packages/world-modules/src/modules/erc721-puppet/tables/TokenApproval.sol @@ -0,0 +1,226 @@ +// 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"; + +FieldLayout constant _fieldLayout = FieldLayout.wrap( + 0x0014010014000000000000000000000000000000000000000000000000000000 +); + +library TokenApproval { + /** + * @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.UINT256; + + 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] = "tokenId"; + } + + /** + * @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] = "account"; + } + + /** + * @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 account. + */ + function getAccount(ResourceId _tableId, uint256 tokenId) internal view returns (address account) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(tokenId)); + + bytes32 _blob = StoreSwitch.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (address(bytes20(_blob))); + } + + /** + * @notice Get account. + */ + function _getAccount(ResourceId _tableId, uint256 tokenId) internal view returns (address account) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(tokenId)); + + bytes32 _blob = StoreCore.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (address(bytes20(_blob))); + } + + /** + * @notice Get account. + */ + function get(ResourceId _tableId, uint256 tokenId) internal view returns (address account) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(tokenId)); + + bytes32 _blob = StoreSwitch.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (address(bytes20(_blob))); + } + + /** + * @notice Get account. + */ + function _get(ResourceId _tableId, uint256 tokenId) internal view returns (address account) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(tokenId)); + + bytes32 _blob = StoreCore.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (address(bytes20(_blob))); + } + + /** + * @notice Set account. + */ + function setAccount(ResourceId _tableId, uint256 tokenId, address account) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(tokenId)); + + StoreSwitch.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((account)), _fieldLayout); + } + + /** + * @notice Set account. + */ + function _setAccount(ResourceId _tableId, uint256 tokenId, address account) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(tokenId)); + + StoreCore.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((account)), _fieldLayout); + } + + /** + * @notice Set account. + */ + function set(ResourceId _tableId, uint256 tokenId, address account) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(tokenId)); + + StoreSwitch.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((account)), _fieldLayout); + } + + /** + * @notice Set account. + */ + function _set(ResourceId _tableId, uint256 tokenId, address account) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(tokenId)); + + StoreCore.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((account)), _fieldLayout); + } + + /** + * @notice Delete all data for given keys. + */ + function deleteRecord(ResourceId _tableId, uint256 tokenId) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(tokenId)); + + StoreSwitch.deleteRecord(_tableId, _keyTuple); + } + + /** + * @notice Delete all data for given keys. + */ + function _deleteRecord(ResourceId _tableId, uint256 tokenId) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(tokenId)); + + 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 account) internal pure returns (bytes memory) { + return abi.encodePacked(account); + } + + /** + * @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 account) internal pure returns (bytes memory, PackedCounter, bytes memory) { + bytes memory _staticData = encodeStatic(account); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; + + return (_staticData, _encodedLengths, _dynamicData); + } + + /** + * @notice Encode keys as a bytes32 array using this table's field layout. + */ + function encodeKeyTuple(uint256 tokenId) internal pure returns (bytes32[] memory) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(tokenId)); + + return _keyTuple; + } +} diff --git a/packages/world-modules/src/modules/erc721-puppet/tables/TokenURI.sol b/packages/world-modules/src/modules/erc721-puppet/tables/TokenURI.sol new file mode 100644 index 0000000000..15ef83a978 --- /dev/null +++ b/packages/world-modules/src/modules/erc721-puppet/tables/TokenURI.sol @@ -0,0 +1,480 @@ +// 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"; + +FieldLayout constant _fieldLayout = FieldLayout.wrap( + 0x0000000100000000000000000000000000000000000000000000000000000000 +); + +library TokenURI { + /** + * @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.UINT256; + + 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.STRING; + + 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] = "tokenId"; + } + + /** + * @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] = "tokenURI"; + } + + /** + * @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 tokenURI. + */ + function getTokenURI(ResourceId _tableId, uint256 tokenId) internal view returns (string memory tokenURI) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(tokenId)); + + bytes memory _blob = StoreSwitch.getDynamicField(_tableId, _keyTuple, 0); + return (string(_blob)); + } + + /** + * @notice Get tokenURI. + */ + function _getTokenURI(ResourceId _tableId, uint256 tokenId) internal view returns (string memory tokenURI) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(tokenId)); + + bytes memory _blob = StoreCore.getDynamicField(_tableId, _keyTuple, 0); + return (string(_blob)); + } + + /** + * @notice Get tokenURI. + */ + function get(ResourceId _tableId, uint256 tokenId) internal view returns (string memory tokenURI) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(tokenId)); + + bytes memory _blob = StoreSwitch.getDynamicField(_tableId, _keyTuple, 0); + return (string(_blob)); + } + + /** + * @notice Get tokenURI. + */ + function _get(ResourceId _tableId, uint256 tokenId) internal view returns (string memory tokenURI) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(tokenId)); + + bytes memory _blob = StoreCore.getDynamicField(_tableId, _keyTuple, 0); + return (string(_blob)); + } + + /** + * @notice Set tokenURI. + */ + function setTokenURI(ResourceId _tableId, uint256 tokenId, string memory tokenURI) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(tokenId)); + + StoreSwitch.setDynamicField(_tableId, _keyTuple, 0, bytes((tokenURI))); + } + + /** + * @notice Set tokenURI. + */ + function _setTokenURI(ResourceId _tableId, uint256 tokenId, string memory tokenURI) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(tokenId)); + + StoreCore.setDynamicField(_tableId, _keyTuple, 0, bytes((tokenURI))); + } + + /** + * @notice Set tokenURI. + */ + function set(ResourceId _tableId, uint256 tokenId, string memory tokenURI) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(tokenId)); + + StoreSwitch.setDynamicField(_tableId, _keyTuple, 0, bytes((tokenURI))); + } + + /** + * @notice Set tokenURI. + */ + function _set(ResourceId _tableId, uint256 tokenId, string memory tokenURI) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(tokenId)); + + StoreCore.setDynamicField(_tableId, _keyTuple, 0, bytes((tokenURI))); + } + + /** + * @notice Get the length of tokenURI. + */ + function lengthTokenURI(ResourceId _tableId, uint256 tokenId) internal view returns (uint256) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(tokenId)); + + uint256 _byteLength = StoreSwitch.getDynamicFieldLength(_tableId, _keyTuple, 0); + unchecked { + return _byteLength / 1; + } + } + + /** + * @notice Get the length of tokenURI. + */ + function _lengthTokenURI(ResourceId _tableId, uint256 tokenId) internal view returns (uint256) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(tokenId)); + + uint256 _byteLength = StoreCore.getDynamicFieldLength(_tableId, _keyTuple, 0); + unchecked { + return _byteLength / 1; + } + } + + /** + * @notice Get the length of tokenURI. + */ + function length(ResourceId _tableId, uint256 tokenId) internal view returns (uint256) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(tokenId)); + + uint256 _byteLength = StoreSwitch.getDynamicFieldLength(_tableId, _keyTuple, 0); + unchecked { + return _byteLength / 1; + } + } + + /** + * @notice Get the length of tokenURI. + */ + function _length(ResourceId _tableId, uint256 tokenId) internal view returns (uint256) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(tokenId)); + + uint256 _byteLength = StoreCore.getDynamicFieldLength(_tableId, _keyTuple, 0); + unchecked { + return _byteLength / 1; + } + } + + /** + * @notice Get an item of tokenURI. + * @dev Reverts with Store_IndexOutOfBounds if `_index` is out of bounds for the array. + */ + function getItemTokenURI(ResourceId _tableId, uint256 tokenId, uint256 _index) internal view returns (string memory) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(tokenId)); + + unchecked { + bytes memory _blob = StoreSwitch.getDynamicFieldSlice(_tableId, _keyTuple, 0, _index * 1, (_index + 1) * 1); + return (string(_blob)); + } + } + + /** + * @notice Get an item of tokenURI. + * @dev Reverts with Store_IndexOutOfBounds if `_index` is out of bounds for the array. + */ + function _getItemTokenURI( + ResourceId _tableId, + uint256 tokenId, + uint256 _index + ) internal view returns (string memory) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(tokenId)); + + unchecked { + bytes memory _blob = StoreCore.getDynamicFieldSlice(_tableId, _keyTuple, 0, _index * 1, (_index + 1) * 1); + return (string(_blob)); + } + } + + /** + * @notice Get an item of tokenURI. + * @dev Reverts with Store_IndexOutOfBounds if `_index` is out of bounds for the array. + */ + function getItem(ResourceId _tableId, uint256 tokenId, uint256 _index) internal view returns (string memory) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(tokenId)); + + unchecked { + bytes memory _blob = StoreSwitch.getDynamicFieldSlice(_tableId, _keyTuple, 0, _index * 1, (_index + 1) * 1); + return (string(_blob)); + } + } + + /** + * @notice Get an item of tokenURI. + * @dev Reverts with Store_IndexOutOfBounds if `_index` is out of bounds for the array. + */ + function _getItem(ResourceId _tableId, uint256 tokenId, uint256 _index) internal view returns (string memory) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(tokenId)); + + unchecked { + bytes memory _blob = StoreCore.getDynamicFieldSlice(_tableId, _keyTuple, 0, _index * 1, (_index + 1) * 1); + return (string(_blob)); + } + } + + /** + * @notice Push a slice to tokenURI. + */ + function pushTokenURI(ResourceId _tableId, uint256 tokenId, string memory _slice) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(tokenId)); + + StoreSwitch.pushToDynamicField(_tableId, _keyTuple, 0, bytes((_slice))); + } + + /** + * @notice Push a slice to tokenURI. + */ + function _pushTokenURI(ResourceId _tableId, uint256 tokenId, string memory _slice) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(tokenId)); + + StoreCore.pushToDynamicField(_tableId, _keyTuple, 0, bytes((_slice))); + } + + /** + * @notice Push a slice to tokenURI. + */ + function push(ResourceId _tableId, uint256 tokenId, string memory _slice) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(tokenId)); + + StoreSwitch.pushToDynamicField(_tableId, _keyTuple, 0, bytes((_slice))); + } + + /** + * @notice Push a slice to tokenURI. + */ + function _push(ResourceId _tableId, uint256 tokenId, string memory _slice) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(tokenId)); + + StoreCore.pushToDynamicField(_tableId, _keyTuple, 0, bytes((_slice))); + } + + /** + * @notice Pop a slice from tokenURI. + */ + function popTokenURI(ResourceId _tableId, uint256 tokenId) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(tokenId)); + + StoreSwitch.popFromDynamicField(_tableId, _keyTuple, 0, 1); + } + + /** + * @notice Pop a slice from tokenURI. + */ + function _popTokenURI(ResourceId _tableId, uint256 tokenId) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(tokenId)); + + StoreCore.popFromDynamicField(_tableId, _keyTuple, 0, 1); + } + + /** + * @notice Pop a slice from tokenURI. + */ + function pop(ResourceId _tableId, uint256 tokenId) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(tokenId)); + + StoreSwitch.popFromDynamicField(_tableId, _keyTuple, 0, 1); + } + + /** + * @notice Pop a slice from tokenURI. + */ + function _pop(ResourceId _tableId, uint256 tokenId) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(tokenId)); + + StoreCore.popFromDynamicField(_tableId, _keyTuple, 0, 1); + } + + /** + * @notice Update a slice of tokenURI at `_index`. + */ + function updateTokenURI(ResourceId _tableId, uint256 tokenId, uint256 _index, string memory _slice) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(tokenId)); + + unchecked { + bytes memory _encoded = bytes((_slice)); + StoreSwitch.spliceDynamicData(_tableId, _keyTuple, 0, uint40(_index * 1), uint40(_encoded.length), _encoded); + } + } + + /** + * @notice Update a slice of tokenURI at `_index`. + */ + function _updateTokenURI(ResourceId _tableId, uint256 tokenId, uint256 _index, string memory _slice) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(tokenId)); + + unchecked { + bytes memory _encoded = bytes((_slice)); + StoreCore.spliceDynamicData(_tableId, _keyTuple, 0, uint40(_index * 1), uint40(_encoded.length), _encoded); + } + } + + /** + * @notice Update a slice of tokenURI at `_index`. + */ + function update(ResourceId _tableId, uint256 tokenId, uint256 _index, string memory _slice) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(tokenId)); + + unchecked { + bytes memory _encoded = bytes((_slice)); + StoreSwitch.spliceDynamicData(_tableId, _keyTuple, 0, uint40(_index * 1), uint40(_encoded.length), _encoded); + } + } + + /** + * @notice Update a slice of tokenURI at `_index`. + */ + function _update(ResourceId _tableId, uint256 tokenId, uint256 _index, string memory _slice) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(tokenId)); + + unchecked { + bytes memory _encoded = bytes((_slice)); + StoreCore.spliceDynamicData(_tableId, _keyTuple, 0, uint40(_index * 1), uint40(_encoded.length), _encoded); + } + } + + /** + * @notice Delete all data for given keys. + */ + function deleteRecord(ResourceId _tableId, uint256 tokenId) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(tokenId)); + + StoreSwitch.deleteRecord(_tableId, _keyTuple); + } + + /** + * @notice Delete all data for given keys. + */ + function _deleteRecord(ResourceId _tableId, uint256 tokenId) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(tokenId)); + + StoreCore.deleteRecord(_tableId, _keyTuple, _fieldLayout); + } + + /** + * @notice Tightly pack dynamic data lengths using this table's schema. + * @return _encodedLengths The lengths of the dynamic fields (packed into a single bytes32 value). + */ + function encodeLengths(string memory tokenURI) internal pure returns (PackedCounter _encodedLengths) { + // Lengths are effectively checked during copy by 2**40 bytes exceeding gas limits + unchecked { + _encodedLengths = PackedCounterLib.pack(bytes(tokenURI).length); + } + } + + /** + * @notice Tightly pack dynamic (variable length) data using this table's schema. + * @return The dynamic data, encoded into a sequence of bytes. + */ + function encodeDynamic(string memory tokenURI) internal pure returns (bytes memory) { + return abi.encodePacked(bytes((tokenURI))); + } + + /** + * @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(string memory tokenURI) internal pure returns (bytes memory, PackedCounter, bytes memory) { + bytes memory _staticData; + PackedCounter _encodedLengths = encodeLengths(tokenURI); + bytes memory _dynamicData = encodeDynamic(tokenURI); + + return (_staticData, _encodedLengths, _dynamicData); + } + + /** + * @notice Encode keys as a bytes32 array using this table's field layout. + */ + function encodeKeyTuple(uint256 tokenId) internal pure returns (bytes32[] memory) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(tokenId)); + + return _keyTuple; + } +} diff --git a/packages/world-modules/src/modules/erc721-puppet/utils.sol b/packages/world-modules/src/modules/erc721-puppet/utils.sol new file mode 100644 index 0000000000..72cf53e742 --- /dev/null +++ b/packages/world-modules/src/modules/erc721-puppet/utils.sol @@ -0,0 +1,42 @@ +// 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 { WorldResourceIdLib } from "@latticexyz/world/src/WorldResourceId.sol"; +import { RESOURCE_SYSTEM } from "@latticexyz/world/src/worldResourceTypes.sol"; + +import { ERC721_SYSTEM_NAME, BALANCES_NAME, METADATA_NAME, OPERATOR_APPROVAL_NAME, OWNERS_NAME, TOKEN_APPROVAL_NAME, TOKEN_URI_NAME } from "./constants.sol"; + +function _balancesTableId(bytes14 namespace) pure returns (ResourceId) { + return WorldResourceIdLib.encode({ typeId: RESOURCE_TABLE, namespace: namespace, name: BALANCES_NAME }); +} + +function _metadataTableId(bytes14 namespace) pure returns (ResourceId) { + return WorldResourceIdLib.encode({ typeId: RESOURCE_TABLE, namespace: namespace, name: METADATA_NAME }); +} + +function _operatorApprovalTableId(bytes14 namespace) pure returns (ResourceId) { + return WorldResourceIdLib.encode({ typeId: RESOURCE_TABLE, namespace: namespace, name: OPERATOR_APPROVAL_NAME }); +} + +function _ownersTableId(bytes14 namespace) pure returns (ResourceId) { + return WorldResourceIdLib.encode({ typeId: RESOURCE_TABLE, namespace: namespace, name: OWNERS_NAME }); +} + +function _tokenApprovalTableId(bytes14 namespace) pure returns (ResourceId) { + return WorldResourceIdLib.encode({ typeId: RESOURCE_TABLE, namespace: namespace, name: TOKEN_APPROVAL_NAME }); +} + +function _tokenUriTableId(bytes14 namespace) pure returns (ResourceId) { + return WorldResourceIdLib.encode({ typeId: RESOURCE_TABLE, namespace: namespace, name: TOKEN_URI_NAME }); +} + +function _toBytes32(address addr) pure returns (bytes32) { + return bytes32(uint256(uint160(addr))); +} + +function _erc721SystemId(bytes14 namespace) pure returns (ResourceId) { + return WorldResourceIdLib.encode({ typeId: RESOURCE_SYSTEM, namespace: namespace, name: ERC721_SYSTEM_NAME }); +} diff --git a/packages/world-modules/src/modules/erc20-puppet/tables/Balances.sol b/packages/world-modules/src/modules/tokens/tables/Balances.sol similarity index 100% rename from packages/world-modules/src/modules/erc20-puppet/tables/Balances.sol rename to packages/world-modules/src/modules/tokens/tables/Balances.sol diff --git a/packages/world-modules/test/ERC20.t.sol b/packages/world-modules/test/ERC20.t.sol index 8b62a34675..9416e8aacd 100644 --- a/packages/world-modules/test/ERC20.t.sol +++ b/packages/world-modules/test/ERC20.t.sol @@ -16,7 +16,7 @@ import { GasReporter } from "@latticexyz/gas-report/src/GasReporter.sol"; import { PuppetModule } from "../src/modules/puppet/PuppetModule.sol"; import { ERC20Module } from "../src/modules/erc20-puppet/ERC20Module.sol"; -import { MetadataData } from "../src/modules/erc20-puppet/tables/Metadata.sol"; +import { ERC20MetadataData } from "../src/modules/erc20-puppet/tables/ERC20Metadata.sol"; import { ERC20Registry } from "../src/modules/erc20-puppet/tables/ERC20Registry.sol"; import { ERC20_REGISTRY_TABLE_ID } from "../src/modules/erc20-puppet/constants.sol"; import { IERC20Mintable } from "../src/modules/erc20-puppet/IERC20Mintable.sol"; @@ -36,7 +36,7 @@ contract ERC20Test is Test, GasReporter, IERC20Events, IERC20Errors { StoreSwitch.setStoreAddress(address(world)); // Register a new ERC20 token - token = registerERC20(world, "myERC20", MetadataData({ decimals: 18, name: "Token", symbol: "TKN" })); + token = registerERC20(world, "myERC20", ERC20MetadataData({ decimals: 18, name: "Token", symbol: "TKN" })); } function testSetUp() public { @@ -49,7 +49,7 @@ contract ERC20Test is Test, GasReporter, IERC20Events, IERC20Errors { IERC20Mintable anotherToken = registerERC20( world, "anotherERC20", - MetadataData({ decimals: 18, name: "Token", symbol: "TKN" }) + ERC20MetadataData({ decimals: 18, name: "Token", symbol: "TKN" }) ); assertTrue(address(anotherToken) != address(0)); assertTrue(address(anotherToken) != address(token)); From 1ee8da8ee13b7060eee695ed63e9d72543647dce Mon Sep 17 00:00:00 2001 From: alvrs Date: Thu, 19 Oct 2023 21:58:03 +0100 Subject: [PATCH 02/27] wip2 --- packages/world-modules/mud.config.ts | 2 +- .../modules/erc721-puppet/ERC721System.sol | 84 +++---------------- .../erc721-puppet/tables/OperatorApproval.sol | 48 +++++------ 3 files changed, 38 insertions(+), 96 deletions(-) diff --git a/packages/world-modules/mud.config.ts b/packages/world-modules/mud.config.ts index 74a5a17553..a830eecd1f 100644 --- a/packages/world-modules/mud.config.ts +++ b/packages/world-modules/mud.config.ts @@ -212,7 +212,7 @@ export default mudConfig({ OperatorApproval: { directory: "modules/erc721-puppet/tables", keySchema: { - tokenId: "uint256", + owner: "address", operator: "address", }, valueSchema: { diff --git a/packages/world-modules/src/modules/erc721-puppet/ERC721System.sol b/packages/world-modules/src/modules/erc721-puppet/ERC721System.sol index 4ad32466e2..b7472bf08d 100644 --- a/packages/world-modules/src/modules/erc721-puppet/ERC721System.sol +++ b/packages/world-modules/src/modules/erc721-puppet/ERC721System.sol @@ -2,12 +2,15 @@ pragma solidity >=0.8.21; import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; +import { IERC165 } from "@openzeppelin/contracts/interfaces/IERC165.sol"; + import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; import { RESOURCE_TABLE } from "@latticexyz/store/src/storeResourceTypes.sol"; import { System } from "@latticexyz/world/src/System.sol"; import { WorldResourceIdInstance, WorldResourceIdLib } 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 { WorldContextConsumer } from "@latticexyz/world/src/WorldContext.sol"; import { AccessControlLib } from "../../utils/AccessControlLib.sol"; import { PuppetMaster } from "../puppet/PuppetMaster.sol"; @@ -23,10 +26,19 @@ import { TokenURI } from "./tables/TokenURI.sol"; import { _balancesTableId, _metadataTableId, _tokenUriTableId, _operatorApprovalTableId, _ownersTableId, _tokenApprovalTableId, _toBytes32 } from "./utils.sol"; -contract ERC721System is System, IERC721Mintable, PuppetMaster { +contract ERC721System is IERC721Mintable, System, PuppetMaster { using Strings for uint256; using WorldResourceIdInstance for ResourceId; + /** + * + */ + function supportsInterface( + bytes4 interfaceId + ) public pure virtual override(WorldContextConsumer, IERC165) returns (bool) { + return interfaceId == type(IERC721Mintable).interfaceId || super.supportsInterface(interfaceId); + } + /** * @dev See {IERC721-balanceOf}. */ @@ -123,21 +135,6 @@ contract ERC721System is System, IERC721Mintable, PuppetMaster { } } - /** - * @dev See {IERC721-safeTransferFrom}. - */ - function safeTransferFrom(address from, address to, uint256 tokenId) public { - safeTransferFrom(from, to, tokenId, ""); - } - - /** - * @dev See {IERC721-safeTransferFrom}. - */ - function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public virtual { - transferFrom(from, to, tokenId); - _checkOnERC721Received(from, to, tokenId, data); - } - /** * @dev Returns the owner of the `tokenId`. Does NOT revert if token doesn't exist * @@ -270,29 +267,6 @@ contract ERC721System is System, IERC721Mintable, PuppetMaster { } } - /** - * @dev Mints `tokenId`, transfers it to `to` and checks for `to` acceptance. - * - * Requirements: - * - * - `tokenId` must not exist. - * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. - * - * Emits a {Transfer} event. - */ - function _safeMint(address to, uint256 tokenId) internal { - _safeMint(to, tokenId, ""); - } - - /** - * @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is - * forwarded in {IERC721Receiver-onERC721Received} to contract recipients. - */ - function _safeMint(address to, uint256 tokenId, bytes memory data) internal virtual { - _mint(to, tokenId); - _checkOnERC721Received(address(0), to, tokenId, data); - } - /** * @dev Destroys `tokenId`. * The approval is cleared when the token is burned. @@ -334,38 +308,6 @@ contract ERC721System is System, IERC721Mintable, PuppetMaster { } } - /** - * @dev Safely transfers `tokenId` token from `from` to `to`, checking that contract recipients - * are aware of the ERC721 standard to prevent tokens from being forever locked. - * - * `data` is additional data, it has no specified format and it is sent in call to `to`. - * - * This internal function is like {safeTransferFrom} in the sense that it invokes - * {IERC721Receiver-onERC721Received} on the receiver, and can be used to e.g. - * implement alternative mechanisms to perform token transfer, such as signature-based. - * - * Requirements: - * - * - `tokenId` token must exist and be owned by `from`. - * - `to` cannot be the zero address. - * - `from` cannot be the zero address. - * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. - * - * Emits a {Transfer} event. - */ - function _safeTransfer(address from, address to, uint256 tokenId) internal { - _safeTransfer(from, to, tokenId, ""); - } - - /** - * @dev Same as {xref-ERC721-_safeTransfer-address-address-uint256-}[`_safeTransfer`], with an additional `data` parameter which is - * forwarded in {IERC721Receiver-onERC721Received} to contract recipients. - */ - function _safeTransfer(address from, address to, uint256 tokenId, bytes memory data) internal virtual { - _transfer(from, to, tokenId); - _checkOnERC721Received(from, to, tokenId, data); - } - /** * @dev Approve `to` to operate on `tokenId` * diff --git a/packages/world-modules/src/modules/erc721-puppet/tables/OperatorApproval.sol b/packages/world-modules/src/modules/erc721-puppet/tables/OperatorApproval.sol index 7d52c0cc15..0cf1a0d775 100644 --- a/packages/world-modules/src/modules/erc721-puppet/tables/OperatorApproval.sol +++ b/packages/world-modules/src/modules/erc721-puppet/tables/OperatorApproval.sol @@ -39,7 +39,7 @@ library OperatorApproval { */ function getKeySchema() internal pure returns (Schema) { SchemaType[] memory _keySchema = new SchemaType[](2); - _keySchema[0] = SchemaType.UINT256; + _keySchema[0] = SchemaType.ADDRESS; _keySchema[1] = SchemaType.ADDRESS; return SchemaLib.encode(_keySchema); @@ -62,7 +62,7 @@ library OperatorApproval { */ function getKeyNames() internal pure returns (string[] memory keyNames) { keyNames = new string[](2); - keyNames[0] = "tokenId"; + keyNames[0] = "owner"; keyNames[1] = "operator"; } @@ -92,9 +92,9 @@ library OperatorApproval { /** * @notice Get approved. */ - function getApproved(ResourceId _tableId, uint256 tokenId, address operator) internal view returns (bool approved) { + function getApproved(ResourceId _tableId, address owner, address operator) internal view returns (bool approved) { bytes32[] memory _keyTuple = new bytes32[](2); - _keyTuple[0] = bytes32(uint256(tokenId)); + _keyTuple[0] = bytes32(uint256(uint160(owner))); _keyTuple[1] = bytes32(uint256(uint160(operator))); bytes32 _blob = StoreSwitch.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); @@ -104,9 +104,9 @@ library OperatorApproval { /** * @notice Get approved. */ - function _getApproved(ResourceId _tableId, uint256 tokenId, address operator) internal view returns (bool approved) { + function _getApproved(ResourceId _tableId, address owner, address operator) internal view returns (bool approved) { bytes32[] memory _keyTuple = new bytes32[](2); - _keyTuple[0] = bytes32(uint256(tokenId)); + _keyTuple[0] = bytes32(uint256(uint160(owner))); _keyTuple[1] = bytes32(uint256(uint160(operator))); bytes32 _blob = StoreCore.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); @@ -116,9 +116,9 @@ library OperatorApproval { /** * @notice Get approved. */ - function get(ResourceId _tableId, uint256 tokenId, address operator) internal view returns (bool approved) { + function get(ResourceId _tableId, address owner, address operator) internal view returns (bool approved) { bytes32[] memory _keyTuple = new bytes32[](2); - _keyTuple[0] = bytes32(uint256(tokenId)); + _keyTuple[0] = bytes32(uint256(uint160(owner))); _keyTuple[1] = bytes32(uint256(uint160(operator))); bytes32 _blob = StoreSwitch.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); @@ -128,9 +128,9 @@ library OperatorApproval { /** * @notice Get approved. */ - function _get(ResourceId _tableId, uint256 tokenId, address operator) internal view returns (bool approved) { + function _get(ResourceId _tableId, address owner, address operator) internal view returns (bool approved) { bytes32[] memory _keyTuple = new bytes32[](2); - _keyTuple[0] = bytes32(uint256(tokenId)); + _keyTuple[0] = bytes32(uint256(uint160(owner))); _keyTuple[1] = bytes32(uint256(uint160(operator))); bytes32 _blob = StoreCore.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); @@ -140,9 +140,9 @@ library OperatorApproval { /** * @notice Set approved. */ - function setApproved(ResourceId _tableId, uint256 tokenId, address operator, bool approved) internal { + function setApproved(ResourceId _tableId, address owner, address operator, bool approved) internal { bytes32[] memory _keyTuple = new bytes32[](2); - _keyTuple[0] = bytes32(uint256(tokenId)); + _keyTuple[0] = bytes32(uint256(uint160(owner))); _keyTuple[1] = bytes32(uint256(uint160(operator))); StoreSwitch.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((approved)), _fieldLayout); @@ -151,9 +151,9 @@ library OperatorApproval { /** * @notice Set approved. */ - function _setApproved(ResourceId _tableId, uint256 tokenId, address operator, bool approved) internal { + function _setApproved(ResourceId _tableId, address owner, address operator, bool approved) internal { bytes32[] memory _keyTuple = new bytes32[](2); - _keyTuple[0] = bytes32(uint256(tokenId)); + _keyTuple[0] = bytes32(uint256(uint160(owner))); _keyTuple[1] = bytes32(uint256(uint160(operator))); StoreCore.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((approved)), _fieldLayout); @@ -162,9 +162,9 @@ library OperatorApproval { /** * @notice Set approved. */ - function set(ResourceId _tableId, uint256 tokenId, address operator, bool approved) internal { + function set(ResourceId _tableId, address owner, address operator, bool approved) internal { bytes32[] memory _keyTuple = new bytes32[](2); - _keyTuple[0] = bytes32(uint256(tokenId)); + _keyTuple[0] = bytes32(uint256(uint160(owner))); _keyTuple[1] = bytes32(uint256(uint160(operator))); StoreSwitch.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((approved)), _fieldLayout); @@ -173,9 +173,9 @@ library OperatorApproval { /** * @notice Set approved. */ - function _set(ResourceId _tableId, uint256 tokenId, address operator, bool approved) internal { + function _set(ResourceId _tableId, address owner, address operator, bool approved) internal { bytes32[] memory _keyTuple = new bytes32[](2); - _keyTuple[0] = bytes32(uint256(tokenId)); + _keyTuple[0] = bytes32(uint256(uint160(owner))); _keyTuple[1] = bytes32(uint256(uint160(operator))); StoreCore.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((approved)), _fieldLayout); @@ -184,9 +184,9 @@ library OperatorApproval { /** * @notice Delete all data for given keys. */ - function deleteRecord(ResourceId _tableId, uint256 tokenId, address operator) internal { + function deleteRecord(ResourceId _tableId, address owner, address operator) internal { bytes32[] memory _keyTuple = new bytes32[](2); - _keyTuple[0] = bytes32(uint256(tokenId)); + _keyTuple[0] = bytes32(uint256(uint160(owner))); _keyTuple[1] = bytes32(uint256(uint160(operator))); StoreSwitch.deleteRecord(_tableId, _keyTuple); @@ -195,9 +195,9 @@ library OperatorApproval { /** * @notice Delete all data for given keys. */ - function _deleteRecord(ResourceId _tableId, uint256 tokenId, address operator) internal { + function _deleteRecord(ResourceId _tableId, address owner, address operator) internal { bytes32[] memory _keyTuple = new bytes32[](2); - _keyTuple[0] = bytes32(uint256(tokenId)); + _keyTuple[0] = bytes32(uint256(uint160(owner))); _keyTuple[1] = bytes32(uint256(uint160(operator))); StoreCore.deleteRecord(_tableId, _keyTuple, _fieldLayout); @@ -229,9 +229,9 @@ library OperatorApproval { /** * @notice Encode keys as a bytes32 array using this table's field layout. */ - function encodeKeyTuple(uint256 tokenId, address operator) internal pure returns (bytes32[] memory) { + function encodeKeyTuple(address owner, address operator) internal pure returns (bytes32[] memory) { bytes32[] memory _keyTuple = new bytes32[](2); - _keyTuple[0] = bytes32(uint256(tokenId)); + _keyTuple[0] = bytes32(uint256(uint160(owner))); _keyTuple[1] = bytes32(uint256(uint160(operator))); return _keyTuple; From 63cb9196f3473568d71c925a2b250bde1b0dcc92 Mon Sep 17 00:00:00 2001 From: alvrs Date: Thu, 19 Oct 2023 22:00:11 +0100 Subject: [PATCH 03/27] add todos --- .../src/modules/erc721-puppet/ERC721System.sol | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/world-modules/src/modules/erc721-puppet/ERC721System.sol b/packages/world-modules/src/modules/erc721-puppet/ERC721System.sol index b7472bf08d..18fe9643b1 100644 --- a/packages/world-modules/src/modules/erc721-puppet/ERC721System.sol +++ b/packages/world-modules/src/modules/erc721-puppet/ERC721System.sol @@ -26,6 +26,14 @@ import { TokenURI } from "./tables/TokenURI.sol"; import { _balancesTableId, _metadataTableId, _tokenUriTableId, _operatorApprovalTableId, _ownersTableId, _tokenApprovalTableId, _toBytes32 } from "./utils.sol"; +/** + * TODO: + * - extend ERC721 to avoid having to redefine all the functions + * - make `mint` and `burn` public with `requireOwner` check + * - Fix up ERC721 Module + * - Add ERC721 tests + */ + contract ERC721System is IERC721Mintable, System, PuppetMaster { using Strings for uint256; using WorldResourceIdInstance for ResourceId; From 63ad4ea13f6e8e59f1b289a23b210861ad442d78 Mon Sep 17 00:00:00 2001 From: alvrs Date: Fri, 20 Oct 2023 12:21:16 +0100 Subject: [PATCH 04/27] wip 3 --- .../world-modules/src/modules/erc721-puppet/ERC721System.sol | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/world-modules/src/modules/erc721-puppet/ERC721System.sol b/packages/world-modules/src/modules/erc721-puppet/ERC721System.sol index 18fe9643b1..0c80e4b944 100644 --- a/packages/world-modules/src/modules/erc721-puppet/ERC721System.sol +++ b/packages/world-modules/src/modules/erc721-puppet/ERC721System.sol @@ -3,6 +3,7 @@ pragma solidity >=0.8.21; import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; import { IERC165 } from "@openzeppelin/contracts/interfaces/IERC165.sol"; +import { Context } from "@openzeppelin/contracts/utils/Context.sol"; import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; import { RESOURCE_TABLE } from "@latticexyz/store/src/storeResourceTypes.sol"; @@ -28,7 +29,6 @@ import { _balancesTableId, _metadataTableId, _tokenUriTableId, _operatorApproval /** * TODO: - * - extend ERC721 to avoid having to redefine all the functions * - make `mint` and `burn` public with `requireOwner` check * - Fix up ERC721 Module * - Add ERC721 tests @@ -38,9 +38,6 @@ contract ERC721System is IERC721Mintable, System, PuppetMaster { using Strings for uint256; using WorldResourceIdInstance for ResourceId; - /** - * - */ function supportsInterface( bytes4 interfaceId ) public pure virtual override(WorldContextConsumer, IERC165) returns (bool) { From f6389c267e2a41e2dc4b6df9025819c2dabe084f Mon Sep 17 00:00:00 2001 From: alvrs Date: Fri, 20 Oct 2023 16:58:14 +0100 Subject: [PATCH 05/27] almost there --- .../src/modules/erc20-puppet/IERC20.sol | 3 +- .../modules/erc20-puppet/IERC20Mintable.sol | 3 +- .../modules/erc721-puppet/ERC721System.sol | 143 ++++++++++++++++-- .../src/modules/erc721-puppet/IERC721.sol | 120 +++++++++++++++ .../modules/erc721-puppet/IERC721Errors.sol | 61 ++++++++ .../modules/erc721-puppet/IERC721Events.sol | 23 +++ .../modules/erc721-puppet/IERC721Metadata.sol | 26 ++++ .../modules/erc721-puppet/IERC721Mintable.sol | 7 +- .../modules/erc721-puppet/IERC721Receiver.sol | 27 ++++ 9 files changed, 393 insertions(+), 20 deletions(-) create mode 100644 packages/world-modules/src/modules/erc721-puppet/IERC721.sol create mode 100644 packages/world-modules/src/modules/erc721-puppet/IERC721Errors.sol create mode 100644 packages/world-modules/src/modules/erc721-puppet/IERC721Events.sol create mode 100644 packages/world-modules/src/modules/erc721-puppet/IERC721Metadata.sol create mode 100644 packages/world-modules/src/modules/erc721-puppet/IERC721Receiver.sol diff --git a/packages/world-modules/src/modules/erc20-puppet/IERC20.sol b/packages/world-modules/src/modules/erc20-puppet/IERC20.sol index 8d10c323da..2129a3dc77 100644 --- a/packages/world-modules/src/modules/erc20-puppet/IERC20.sol +++ b/packages/world-modules/src/modules/erc20-puppet/IERC20.sol @@ -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. */ diff --git a/packages/world-modules/src/modules/erc20-puppet/IERC20Mintable.sol b/packages/world-modules/src/modules/erc20-puppet/IERC20Mintable.sol index 014069ee08..3e5ece4d28 100644 --- a/packages/world-modules/src/modules/erc20-puppet/IERC20Mintable.sol +++ b/packages/world-modules/src/modules/erc20-puppet/IERC20Mintable.sol @@ -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). * diff --git a/packages/world-modules/src/modules/erc721-puppet/ERC721System.sol b/packages/world-modules/src/modules/erc721-puppet/ERC721System.sol index 0c80e4b944..30265ce385 100644 --- a/packages/world-modules/src/modules/erc721-puppet/ERC721System.sol +++ b/packages/world-modules/src/modules/erc721-puppet/ERC721System.sol @@ -1,10 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.21; -import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; -import { IERC165 } from "@openzeppelin/contracts/interfaces/IERC165.sol"; -import { Context } from "@openzeppelin/contracts/utils/Context.sol"; - import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; import { RESOURCE_TABLE } from "@latticexyz/store/src/storeResourceTypes.sol"; import { System } from "@latticexyz/world/src/System.sol"; @@ -18,6 +14,7 @@ import { PuppetMaster } from "../puppet/PuppetMaster.sol"; import { Balances } from "../tokens/tables/Balances.sol"; import { IERC721Mintable } from "./IERC721Mintable.sol"; +import { IERC721Receiver } from "./IERC721Receiver.sol"; import { ERC721Metadata } from "./tables/ERC721Metadata.sol"; import { OperatorApproval } from "./tables/OperatorApproval.sol"; @@ -35,15 +32,8 @@ import { _balancesTableId, _metadataTableId, _tokenUriTableId, _operatorApproval */ contract ERC721System is IERC721Mintable, System, PuppetMaster { - using Strings for uint256; using WorldResourceIdInstance for ResourceId; - function supportsInterface( - bytes4 interfaceId - ) public pure virtual override(WorldContextConsumer, IERC165) returns (bool) { - return interfaceId == type(IERC721Mintable).interfaceId || super.supportsInterface(interfaceId); - } - /** * @dev See {IERC721-balanceOf}. */ @@ -83,7 +73,7 @@ contract ERC721System is IERC721Mintable, System, PuppetMaster { string memory baseURI = _baseURI(); string memory _tokenURI = TokenURI.get(_tokenUriTableId(_namespace()), tokenId); - _tokenURI = bytes(_tokenURI).length > 0 ? _tokenURI : tokenId.toString(); + _tokenURI = bytes(_tokenURI).length > 0 ? _tokenURI : string(abi.encodePacked(tokenId)); return bytes(baseURI).length > 0 ? string.concat(baseURI, _tokenURI) : _tokenURI; } @@ -140,6 +130,52 @@ contract ERC721System is IERC721Mintable, System, PuppetMaster { } } + /** + * @dev See {IERC721-safeTransferFrom}. + */ + function safeTransferFrom(address from, address to, uint256 tokenId) public { + safeTransferFrom(from, to, tokenId, ""); + } + + /** + * @dev See {IERC721-safeTransferFrom}. + */ + function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public virtual { + transferFrom(from, to, tokenId); + _checkOnERC721Received(from, to, tokenId, data); + } + + /** + * @dev Mints `tokenId` and transfers it to `to`. + * + * Requirements: + * + * - caller must own the namespace + * - `tokenId` must not exist. + * - `to` cannot be the zero address. + * + * Emits a {Transfer} event. + */ + function mint(address to, uint256 tokenId) public virtual { + _requireOwner(); + _mint(to, tokenId); + } + + /** + * @dev Destroys `tokenId`. + * The approval is cleared when the token is burned. + * + * Requirements: + * - caller must own the namespace + * - `tokenId` must exist. + * + * Emits a {Transfer} event. + */ + function burn(uint256 tokenId) public { + _requireOwner(); + _burn(tokenId); + } + /** * @dev Returns the owner of the `tokenId`. Does NOT revert if token doesn't exist * @@ -272,6 +308,29 @@ contract ERC721System is IERC721Mintable, System, PuppetMaster { } } + /** + * @dev Mints `tokenId`, transfers it to `to` and checks for `to` acceptance. + * + * Requirements: + * + * - `tokenId` must not exist. + * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. + * + * Emits a {Transfer} event. + */ + function _safeMint(address to, uint256 tokenId) internal { + _safeMint(to, tokenId, ""); + } + + /** + * @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is + * forwarded in {IERC721Receiver-onERC721Received} to contract recipients. + */ + function _safeMint(address to, uint256 tokenId, bytes memory data) internal virtual { + _mint(to, tokenId); + _checkOnERC721Received(address(0), to, tokenId, data); + } + /** * @dev Destroys `tokenId`. * The approval is cleared when the token is burned. @@ -313,6 +372,38 @@ contract ERC721System is IERC721Mintable, System, PuppetMaster { } } + /** + * @dev Safely transfers `tokenId` token from `from` to `to`, checking that contract recipients + * are aware of the ERC721 standard to prevent tokens from being forever locked. + * + * `data` is additional data, it has no specified format and it is sent in call to `to`. + * + * This internal function is like {safeTransferFrom} in the sense that it invokes + * {IERC721Receiver-onERC721Received} on the receiver, and can be used to e.g. + * implement alternative mechanisms to perform token transfer, such as signature-based. + * + * Requirements: + * + * - `tokenId` token must exist and be owned by `from`. + * - `to` cannot be the zero address. + * - `from` cannot be the zero address. + * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. + * + * Emits a {Transfer} event. + */ + function _safeTransfer(address from, address to, uint256 tokenId) internal { + _safeTransfer(from, to, tokenId, ""); + } + + /** + * @dev Same as {xref-ERC721-_safeTransfer-address-address-uint256-}[`_safeTransfer`], with an additional `data` parameter which is + * forwarded in {IERC721Receiver-onERC721Received} to contract recipients. + */ + function _safeTransfer(address from, address to, uint256 tokenId, bytes memory data) internal virtual { + _transfer(from, to, tokenId); + _checkOnERC721Received(from, to, tokenId, data); + } + /** * @dev Approve `to` to operate on `tokenId` * @@ -379,6 +470,34 @@ contract ERC721System is IERC721Mintable, System, PuppetMaster { return owner; } + /** + * @dev Private function to invoke {IERC721Receiver-onERC721Received} on a target address. This will revert if the + * recipient doesn't accept the token transfer. The call is not executed if the target address is not a contract. + * + * @param from address representing the previous owner of the given token ID + * @param to target address that will receive the tokens + * @param tokenId uint256 ID of the token to be transferred + * @param data bytes optional data to send along with the call + */ + function _checkOnERC721Received(address from, address to, uint256 tokenId, bytes memory data) private { + if (to.code.length > 0) { + try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, data) returns (bytes4 retval) { + if (retval != IERC721Receiver.onERC721Received.selector) { + revert ERC721InvalidReceiver(to); + } + } catch (bytes memory reason) { + if (reason.length == 0) { + revert ERC721InvalidReceiver(to); + } else { + /// @solidity memory-safe-assembly + assembly { + revert(add(32, reason), mload(reason)) + } + } + } + } + } + function _namespace() internal view returns (bytes14 namespace) { ResourceId systemId = SystemRegistry.get(address(this)); return systemId.getNamespace(); diff --git a/packages/world-modules/src/modules/erc721-puppet/IERC721.sol b/packages/world-modules/src/modules/erc721-puppet/IERC721.sol new file mode 100644 index 0000000000..9d42965d5a --- /dev/null +++ b/packages/world-modules/src/modules/erc721-puppet/IERC721.sol @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/IERC721.sol) +pragma solidity >=0.8.21; + +import { IERC721Events } from "./IERC721Events.sol"; +import { IERC721Errors } from "./IERC721Errors.sol"; + +/** + * @dev Required interface of an ERC721 compliant contract. + */ +interface IERC721 is IERC721Events, IERC721Errors { + /** + * @dev Returns the number of tokens in ``owner``'s account. + */ + function balanceOf(address owner) external view returns (uint256 balance); + + /** + * @dev Returns the owner of the `tokenId` token. + * + * Requirements: + * + * - `tokenId` must exist. + */ + function ownerOf(uint256 tokenId) external view returns (address owner); + + /** + * @dev Safely transfers `tokenId` token from `from` to `to`. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `tokenId` token must exist and be owned by `from`. + * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. + * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon + * a safe transfer. + * + * Emits a {Transfer} event. + */ + function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external; + + /** + * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients + * are aware of the ERC721 protocol to prevent tokens from being forever locked. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `tokenId` token must exist and be owned by `from`. + * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or + * {setApprovalForAll}. + * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon + * a safe transfer. + * + * Emits a {Transfer} event. + */ + function safeTransferFrom(address from, address to, uint256 tokenId) external; + + /** + * @dev Transfers `tokenId` token from `from` to `to`. + * + * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721 + * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must + * understand this adds an external call which potentially creates a reentrancy vulnerability. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `tokenId` token must be owned by `from`. + * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. + * + * Emits a {Transfer} event. + */ + function transferFrom(address from, address to, uint256 tokenId) external; + + /** + * @dev Gives permission to `to` to transfer `tokenId` token to another account. + * The approval is cleared when the token is transferred. + * + * Only a single account can be approved at a time, so approving the zero address clears previous approvals. + * + * Requirements: + * + * - The caller must own the token or be an approved operator. + * - `tokenId` must exist. + * + * Emits an {Approval} event. + */ + function approve(address to, uint256 tokenId) external; + + /** + * @dev Approve or remove `operator` as an operator for the caller. + * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller. + * + * Requirements: + * + * - The `operator` cannot be the address zero. + * + * Emits an {ApprovalForAll} event. + */ + function setApprovalForAll(address operator, bool approved) external; + + /** + * @dev Returns the account approved for `tokenId` token. + * + * Requirements: + * + * - `tokenId` must exist. + */ + function getApproved(uint256 tokenId) external view returns (address operator); + + /** + * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`. + * + * See {setApprovalForAll} + */ + function isApprovedForAll(address owner, address operator) external view returns (bool); +} diff --git a/packages/world-modules/src/modules/erc721-puppet/IERC721Errors.sol b/packages/world-modules/src/modules/erc721-puppet/IERC721Errors.sol new file mode 100644 index 0000000000..5e287daec0 --- /dev/null +++ b/packages/world-modules/src/modules/erc721-puppet/IERC721Errors.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/draft-IERC6093.sol) +pragma solidity >=0.8.21; + +/** + * @dev Standard ERC721 Errors + * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC721 tokens. + */ +interface IERC721Errors { + /** + * @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in EIP-20. + * Used in balance queries. + * @param owner Address of the current owner of a token. + */ + error ERC721InvalidOwner(address owner); + + /** + * @dev Indicates a `tokenId` whose `owner` is the zero address. + * @param tokenId Identifier number of a token. + */ + error ERC721NonexistentToken(uint256 tokenId); + + /** + * @dev Indicates an error related to the ownership over a particular token. Used in transfers. + * @param sender Address whose tokens are being transferred. + * @param tokenId Identifier number of a token. + * @param owner Address of the current owner of a token. + */ + error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner); + + /** + * @dev Indicates a failure with the token `sender`. Used in transfers. + * @param sender Address whose tokens are being transferred. + */ + error ERC721InvalidSender(address sender); + + /** + * @dev Indicates a failure with the token `receiver`. Used in transfers. + * @param receiver Address to which tokens are being transferred. + */ + error ERC721InvalidReceiver(address receiver); + + /** + * @dev Indicates a failure with the `operator`’s approval. Used in transfers. + * @param operator Address that may be allowed to operate on tokens without being their owner. + * @param tokenId Identifier number of a token. + */ + error ERC721InsufficientApproval(address operator, uint256 tokenId); + + /** + * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals. + * @param approver Address initiating an approval operation. + */ + error ERC721InvalidApprover(address approver); + + /** + * @dev Indicates a failure with the `operator` to be approved. Used in approvals. + * @param operator Address that may be allowed to operate on tokens without being their owner. + */ + error ERC721InvalidOperator(address operator); +} diff --git a/packages/world-modules/src/modules/erc721-puppet/IERC721Events.sol b/packages/world-modules/src/modules/erc721-puppet/IERC721Events.sol new file mode 100644 index 0000000000..fde5124465 --- /dev/null +++ b/packages/world-modules/src/modules/erc721-puppet/IERC721Events.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/IERC721.sol) +pragma solidity >=0.8.21; + +/** + * @dev Events emitted by an ERC721 compliant contract. + */ +interface IERC721Events { + /** + * @dev Emitted when `tokenId` token is transferred from `from` to `to`. + */ + event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); + + /** + * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token. + */ + event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); + + /** + * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets. + */ + event ApprovalForAll(address indexed owner, address indexed operator, bool approved); +} diff --git a/packages/world-modules/src/modules/erc721-puppet/IERC721Metadata.sol b/packages/world-modules/src/modules/erc721-puppet/IERC721Metadata.sol new file mode 100644 index 0000000000..fd6621cf14 --- /dev/null +++ b/packages/world-modules/src/modules/erc721-puppet/IERC721Metadata.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/extensions/IERC721Metadata.sol) +pragma solidity >=0.8.21; + +import { IERC721 } from "./IERC721.sol"; + +/** + * @title ERC-721 Non-Fungible Token Standard, optional metadata extension + * @dev See https://eips.ethereum.org/EIPS/eip-721 + */ +interface IERC721Metadata is IERC721 { + /** + * @dev Returns the token collection name. + */ + function name() external view returns (string memory); + + /** + * @dev Returns the token collection symbol. + */ + function symbol() external view returns (string memory); + + /** + * @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token. + */ + function tokenURI(uint256 tokenId) external view returns (string memory); +} diff --git a/packages/world-modules/src/modules/erc721-puppet/IERC721Mintable.sol b/packages/world-modules/src/modules/erc721-puppet/IERC721Mintable.sol index 88a066b943..b09f6fd88a 100644 --- a/packages/world-modules/src/modules/erc721-puppet/IERC721Mintable.sol +++ b/packages/world-modules/src/modules/erc721-puppet/IERC721Mintable.sol @@ -1,16 +1,13 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol) - pragma solidity >=0.8.21; -import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; -import { IERC721Metadata } from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol"; -import { IERC721Errors } from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol"; +import { IERC721 } from "./IERC721.sol"; /** * @dev Extending the ERC721 standard with permissioned mint and burn functions. */ -interface IERC721Mintable is IERC721, IERC721Metadata, IERC721Errors { +interface IERC721Mintable is IERC721 { /** * @dev Mints `tokenId` and transfers it to `to`. * diff --git a/packages/world-modules/src/modules/erc721-puppet/IERC721Receiver.sol b/packages/world-modules/src/modules/erc721-puppet/IERC721Receiver.sol new file mode 100644 index 0000000000..f974fab71d --- /dev/null +++ b/packages/world-modules/src/modules/erc721-puppet/IERC721Receiver.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/IERC721Receiver.sol) +pragma solidity >=0.8.21; + +/** + * @title ERC721 token receiver interface + * @dev Interface for any contract that wants to support safeTransfers + * from ERC721 asset contracts. + */ +interface IERC721Receiver { + /** + * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom} + * by `operator` from `from`, this function is called. + * + * It must return its Solidity selector to confirm the token transfer. + * If any other value is returned or the interface is not implemented by the recipient, the transfer will be + * reverted. + * + * The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`. + */ + function onERC721Received( + address operator, + address from, + uint256 tokenId, + bytes calldata data + ) external returns (bytes4); +} From 10c2aaf1dd6f3bd766036b82971150cff4d909fc Mon Sep 17 00:00:00 2001 From: alvrs Date: Mon, 23 Oct 2023 20:35:28 +0100 Subject: [PATCH 06/27] fix ERC721 module --- .../modules/erc721-puppet/ERC721Module.sol | 58 +++++++++++-------- .../modules/erc721-puppet/ERC721System.sol | 2 - 2 files changed, 33 insertions(+), 27 deletions(-) diff --git a/packages/world-modules/src/modules/erc721-puppet/ERC721Module.sol b/packages/world-modules/src/modules/erc721-puppet/ERC721Module.sol index 3372547da3..579529e98f 100644 --- a/packages/world-modules/src/modules/erc721-puppet/ERC721Module.sol +++ b/packages/world-modules/src/modules/erc721-puppet/ERC721Module.sol @@ -14,34 +14,42 @@ import { MODULE_NAME as PUPPET_MODULE_NAME } from "../puppet/constants.sol"; import { PuppetModule } from "../puppet/PuppetModule.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 { MODULE_NAME, MODULE_NAMESPACE, MODULE_NAMESPACE_ID, ERC721_REGISTRY_TABLE_ID } from "./constants.sol"; +import { _erc721SystemId, _balancesTableId, _metadataTableId, _tokenUriTableId, _operatorApprovalTableId, _ownersTableId, _tokenApprovalTableId } from "./utils.sol"; import { ERC721System } from "./ERC721System.sol"; +import { OperatorApproval } from "./tables/OperatorApproval.sol"; +import { Owners } from "./tables/Owners.sol"; +import { TokenApproval } from "./tables/TokenApproval.sol"; +import { TokenURI } from "./tables/TokenURI.sol"; import { ERC721Registry } from "./tables/ERC721Registry.sol"; -import { ERC721Metadata, ERC721MetadataData } from "./tables/Metadata.sol"; +import { ERC721Metadata, ERC721MetadataData } from "./tables/ERC721Metadata.sol"; -contract ERC20Module is Module { - error ERC20Module_InvalidNamespace(bytes14 namespace); +contract ERC721Module is Module { + error ERC721Module_InvalidNamespace(bytes14 namespace); function getName() public pure override returns (bytes16) { return MODULE_NAME; } /** - * Register systems and tables for a new ERC20 token in a given namespace + * Register systems and tables for a new ERC721 token in a given namespace */ - function _registerERC20(bytes14 namespace) internal { + function _registerERC721(bytes14 namespace) internal { // Register the tables - Allowances.register(_allowancesTableId(namespace)); + + OperatorApproval.register(_operatorApprovalTableId(namespace)); + Owners.register(_ownersTableId(namespace)); + TokenApproval.register(_tokenApprovalTableId(namespace)); + TokenURI.register(_tokenUriTableId(namespace)); Balances.register(_balancesTableId(namespace)); - Metadata.register(_metadataTableId(namespace)); + ERC721Metadata.register(_metadataTableId(namespace)); // Register a new ERC20System - IBaseWorld(_world()).registerSystem(_erc20SystemId(namespace), new ERC20System(), true); + IBaseWorld(_world()).registerSystem(_erc721SystemId(namespace), new ERC721System(), true); } - function _installDependencies() internal { + function _requireDependencies() internal { // If the PuppetModule is not installed yet, install it if (InstalledModules.get(PUPPET_MODULE_NAME, keccak256(new bytes(0))) == address(0)) { IBaseWorld(_world()).installModule(new PuppetModule(), new bytes(0)); @@ -55,36 +63,36 @@ contract ERC20Module is Module { } // Extract args - (bytes14 namespace, MetadataData memory metadata) = abi.decode(args, (bytes14, MetadataData)); + (bytes14 namespace, ERC721MetadataData memory metadata) = abi.decode(args, (bytes14, ERC721MetadataData)); // Require the namespace to not be the module's namespace if (namespace == MODULE_NAMESPACE) { - revert ERC20Module_InvalidNamespace(namespace); + revert ERC721Module_InvalidNamespace(namespace); } - // Install dependencies - _installDependencies(); + // Require dependencies + _requireDependencies(); - // Register the ERC20 tables and system - _registerERC20(namespace); + // Register the ERC721 tables and system + _registerERC721(namespace); // Initialize the Metadata - Metadata.set(_metadataTableId(namespace), metadata); + ERC721Metadata.set(_metadataTableId(namespace), metadata); - // Deploy and register the ERC20 puppet. + // Deploy and register the ERC721 puppet. IBaseWorld world = IBaseWorld(_world()); - ResourceId erc20SystemId = _erc20SystemId(namespace); - address puppet = createPuppet(world, erc20SystemId); + ResourceId erc721SystemId = _erc721SystemId(namespace); + address puppet = createPuppet(world, erc721SystemId); // Transfer ownership of the namespace to the caller ResourceId namespaceId = WorldResourceIdLib.encodeNamespace(namespace); world.transferOwnership(namespaceId, _msgSender()); - // Register the ERC20 in the ERC20Registry - if (!ResourceIds.getExists(ERC20_REGISTRY_TABLE_ID)) { - ERC20Registry.register(ERC20_REGISTRY_TABLE_ID); + // Register the ERC721 in the ERC20Registry + if (!ResourceIds.getExists(ERC721_REGISTRY_TABLE_ID)) { + ERC721Registry.register(ERC721_REGISTRY_TABLE_ID); } - ERC20Registry.set(ERC20_REGISTRY_TABLE_ID, namespaceId, puppet); + ERC721Registry.set(ERC721_REGISTRY_TABLE_ID, namespaceId, puppet); } function installRoot(bytes memory) public pure { diff --git a/packages/world-modules/src/modules/erc721-puppet/ERC721System.sol b/packages/world-modules/src/modules/erc721-puppet/ERC721System.sol index 30265ce385..dbf42d9afe 100644 --- a/packages/world-modules/src/modules/erc721-puppet/ERC721System.sol +++ b/packages/world-modules/src/modules/erc721-puppet/ERC721System.sol @@ -26,8 +26,6 @@ import { _balancesTableId, _metadataTableId, _tokenUriTableId, _operatorApproval /** * TODO: - * - make `mint` and `burn` public with `requireOwner` check - * - Fix up ERC721 Module * - Add ERC721 tests */ From 611d722dd3f9be052f279ad814b6ddaf28c87e93 Mon Sep 17 00:00:00 2001 From: alvrs Date: Wed, 25 Oct 2023 14:15:19 +0100 Subject: [PATCH 07/27] wip: add erc721 test --- packages/world-modules/test/ERC721.t.sol | 329 +++++++++++++++++++++++ 1 file changed, 329 insertions(+) create mode 100644 packages/world-modules/test/ERC721.t.sol diff --git a/packages/world-modules/test/ERC721.t.sol b/packages/world-modules/test/ERC721.t.sol new file mode 100644 index 0000000000..5e31cc71b6 --- /dev/null +++ b/packages/world-modules/test/ERC721.t.sol @@ -0,0 +1,329 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.21; + +import { Test } from "forge-std/Test.sol"; +import { console } from "forge-std/console.sol"; + +import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; +import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol"; +import { World } from "@latticexyz/world/src/World.sol"; +import { WorldResourceIdLib } from "@latticexyz/world/src/WorldResourceId.sol"; +import { CoreModule } from "@latticexyz/world/src/modules/core/CoreModule.sol"; +import { IBaseWorld } from "@latticexyz/world/src/codegen/interfaces/IBaseWorld.sol"; +import { NamespaceOwner } from "@latticexyz/world/src/codegen/tables/NamespaceOwner.sol"; +import { GasReporter } from "@latticexyz/gas-report/src/GasReporter.sol"; + +import { PuppetModule } from "../src/modules/puppet/PuppetModule.sol"; + +import { ERC20Module } from "../src/modules/erc20-puppet/ERC20Module.sol"; +import { ERC20MetadataData } from "../src/modules/erc20-puppet/tables/ERC20Metadata.sol"; +import { ERC20Registry } from "../src/modules/erc20-puppet/tables/ERC20Registry.sol"; +import { ERC20_REGISTRY_TABLE_ID } from "../src/modules/erc20-puppet/constants.sol"; +import { IERC20Mintable } from "../src/modules/erc20-puppet/IERC20Mintable.sol"; +import { registerERC20 } from "../src/modules/erc20-puppet/registerERC20.sol"; +import { IERC20Errors } from "../src/modules/erc20-puppet/IERC20Errors.sol"; + +import { IERC20Events } from "./IERC20Events.sol"; + +contract ERC20Test is Test, GasReporter, IERC20Events, IERC20Errors { + IBaseWorld world; + ERC20Module erc20Module; + IERC20Mintable token; + + function setUp() public { + world = IBaseWorld(address(new World())); + world.initialize(new CoreModule()); + world.installModule(new PuppetModule(), new bytes(0)); + StoreSwitch.setStoreAddress(address(world)); + + // Register a new ERC20 token + token = registerERC20(world, "myERC20", ERC20MetadataData({ decimals: 18, name: "Token", symbol: "TKN" })); + } + + function testSetUp() public { + assertTrue(address(token) != address(0)); + assertEq(NamespaceOwner.get(WorldResourceIdLib.encodeNamespace("myERC20")), address(this)); + } + + function testInstallTwice() public { + // Install the ERC20 module + IERC20Mintable anotherToken = registerERC20( + world, + "anotherERC20", + ERC20MetadataData({ decimals: 18, name: "Token", symbol: "TKN" }) + ); + assertTrue(address(anotherToken) != address(0)); + assertTrue(address(anotherToken) != address(token)); + } + + ///////////////////////////////////////////////// + // SOLADY ERC20 TEST CAES + // (https://github.com/Vectorized/solady/blob/main/test/ERC20.t.sol) + ///////////////////////////////////////////////// + + function testMetadata() public { + assertEq(token.name(), "Token"); + assertEq(token.symbol(), "TKN"); + assertEq(token.decimals(), 18); + } + + function testMint() public { + vm.expectEmit(true, true, true, true); + emit Transfer(address(0), address(0xBEEF), 1e18); + startGasReport("mint"); + token.mint(address(0xBEEF), 1e18); + endGasReport(); + + assertEq(token.totalSupply(), 1e18); + assertEq(token.balanceOf(address(0xBEEF)), 1e18); + } + + function testBurn() public { + token.mint(address(0xBEEF), 1e18); + + vm.expectEmit(true, true, true, true); + emit Transfer(address(0xBEEF), address(0), 0.9e18); + startGasReport("burn"); + token.burn(address(0xBEEF), 0.9e18); + endGasReport(); + + assertEq(token.totalSupply(), 1e18 - 0.9e18); + assertEq(token.balanceOf(address(0xBEEF)), 0.1e18); + } + + function testApprove() public { + vm.expectEmit(true, true, true, true); + emit Approval(address(this), address(0xBEEF), 1e18); + startGasReport("approve"); + bool success = token.approve(address(0xBEEF), 1e18); + endGasReport(); + assertTrue(success); + + assertEq(token.allowance(address(this), address(0xBEEF)), 1e18); + } + + function testTransfer() public { + token.mint(address(this), 1e18); + + vm.expectEmit(true, true, true, true); + emit Transfer(address(this), address(0xBEEF), 1e18); + startGasReport("transfer"); + bool success = token.transfer(address(0xBEEF), 1e18); + endGasReport(); + assertTrue(success); + assertEq(token.totalSupply(), 1e18); + + assertEq(token.balanceOf(address(this)), 0); + assertEq(token.balanceOf(address(0xBEEF)), 1e18); + } + + function testTransferFrom() public { + address from = address(0xABCD); + + token.mint(from, 1e18); + + vm.prank(from); + token.approve(address(this), 1e18); + + vm.expectEmit(true, true, true, true); + emit Transfer(from, address(0xBEEF), 1e18); + startGasReport("transferFrom"); + bool success = token.transferFrom(from, address(0xBEEF), 1e18); + endGasReport(); + assertTrue(success); + assertEq(token.totalSupply(), 1e18); + + assertEq(token.allowance(from, address(this)), 0); + + assertEq(token.balanceOf(from), 0); + assertEq(token.balanceOf(address(0xBEEF)), 1e18); + } + + function testInfiniteApproveTransferFrom() public { + address from = address(0xABCD); + + token.mint(from, 1e18); + + vm.prank(from); + token.approve(address(this), type(uint256).max); + + assertTrue(token.transferFrom(from, address(0xBEEF), 1e18)); + assertEq(token.totalSupply(), 1e18); + + assertEq(token.allowance(from, address(this)), type(uint256).max); + + assertEq(token.balanceOf(from), 0); + assertEq(token.balanceOf(address(0xBEEF)), 1e18); + } + + function testMintOverMaxUintReverts() public { + token.mint(address(this), type(uint256).max); + vm.expectRevert(); + token.mint(address(this), 1); + } + + function testTransferInsufficientBalanceReverts() public { + token.mint(address(this), 0.9e18); + vm.expectRevert( + abi.encodeWithSelector(IERC20Errors.ERC20InsufficientBalance.selector, address(this), 0.9e18, 1e18) + ); + token.transfer(address(0xBEEF), 1e18); + } + + function testTransferFromInsufficientAllowanceReverts() public { + address from = address(0xABCD); + + token.mint(from, 1e18); + + vm.prank(from); + token.approve(address(this), 0.9e18); + + vm.expectRevert(abi.encodeWithSelector(ERC20InsufficientAllowance.selector, address(this), 0.9e18, 1e18)); + token.transferFrom(from, address(0xBEEF), 1e18); + } + + function testTransferFromInsufficientBalanceReverts() public { + address from = address(0xABCD); + + token.mint(from, 0.9e18); + + vm.prank(from); + token.approve(address(this), 1e18); + + vm.expectRevert(abi.encodeWithSelector(ERC20InsufficientBalance.selector, from, 0.9e18, 1e18)); + token.transferFrom(from, address(0xBEEF), 1e18); + } + + function testMint(address to, uint256 amount) public { + vm.assume(to != address(0)); + + vm.expectEmit(true, true, true, true); + emit Transfer(address(0), to, amount); + token.mint(to, amount); + + assertEq(token.totalSupply(), amount); + assertEq(token.balanceOf(to), amount); + } + + function testBurn(address from, uint256 mintAmount, uint256 burnAmount) public { + vm.assume(from != address(0)); + vm.assume(burnAmount <= mintAmount); + + token.mint(from, mintAmount); + vm.expectEmit(true, true, true, true); + emit Transfer(from, address(0), burnAmount); + token.burn(from, burnAmount); + + assertEq(token.totalSupply(), mintAmount - burnAmount); + assertEq(token.balanceOf(from), mintAmount - burnAmount); + } + + function testApprove(address to, uint256 amount) public { + vm.assume(to != address(0)); + + assertTrue(token.approve(to, amount)); + + assertEq(token.allowance(address(this), to), amount); + } + + function testTransfer(address to, uint256 amount) public { + vm.assume(to != address(0)); + token.mint(address(this), amount); + + vm.expectEmit(true, true, true, true); + emit Transfer(address(this), to, amount); + assertTrue(token.transfer(to, amount)); + assertEq(token.totalSupply(), amount); + + if (address(this) == to) { + assertEq(token.balanceOf(address(this)), amount); + } else { + assertEq(token.balanceOf(address(this)), 0); + assertEq(token.balanceOf(to), amount); + } + } + + function testTransferFrom(address spender, address from, address to, uint256 approval, uint256 amount) public { + vm.assume(from != address(0)); + vm.assume(to != address(0)); + vm.assume(spender != address(0)); + vm.assume(amount <= approval); + + token.mint(from, amount); + assertEq(token.balanceOf(from), amount); + + vm.prank(from); + token.approve(spender, approval); + + vm.expectEmit(true, true, true, true); + emit Transfer(from, to, amount); + vm.prank(spender); + assertTrue(token.transferFrom(from, to, amount)); + assertEq(token.totalSupply(), amount); + + if (approval == type(uint256).max) { + assertEq(token.allowance(from, spender), approval); + } else { + assertEq(token.allowance(from, spender), approval - amount); + } + + if (from == to) { + assertEq(token.balanceOf(from), amount); + } else { + assertEq(token.balanceOf(from), 0); + assertEq(token.balanceOf(to), amount); + } + } + + function testBurnInsufficientBalanceReverts(address to, uint256 mintAmount, uint256 burnAmount) public { + vm.assume(to != address(0)); + vm.assume(mintAmount < type(uint256).max); + vm.assume(burnAmount > mintAmount); + + token.mint(to, mintAmount); + vm.expectRevert(abi.encodeWithSelector(ERC20InsufficientBalance.selector, to, mintAmount, burnAmount)); + token.burn(to, burnAmount); + } + + function testTransferInsufficientBalanceReverts(address to, uint256 mintAmount, uint256 sendAmount) public { + vm.assume(to != address(0)); + vm.assume(mintAmount < type(uint256).max); + vm.assume(sendAmount > mintAmount); + + token.mint(address(this), mintAmount); + vm.expectRevert(abi.encodeWithSelector(ERC20InsufficientBalance.selector, address(this), mintAmount, sendAmount)); + token.transfer(to, sendAmount); + } + + function testTransferFromInsufficientAllowanceReverts(address to, uint256 approval, uint256 amount) public { + vm.assume(to != address(0)); + vm.assume(approval < type(uint256).max); + vm.assume(amount > approval); + + address from = address(0xABCD); + + token.mint(from, amount); + + vm.prank(from); + token.approve(address(this), approval); + + vm.expectRevert(abi.encodeWithSelector(ERC20InsufficientAllowance.selector, address(this), approval, amount)); + token.transferFrom(from, to, amount); + } + + function testTransferFromInsufficientBalanceReverts(address to, uint256 mintAmount, uint256 sendAmount) public { + vm.assume(to != address(0)); + vm.assume(mintAmount < type(uint256).max); + vm.assume(sendAmount > mintAmount); + + address from = address(0xABCD); + + token.mint(from, mintAmount); + + vm.prank(from); + token.approve(address(this), sendAmount); + + vm.expectRevert(abi.encodeWithSelector(ERC20InsufficientBalance.selector, from, mintAmount, sendAmount)); + token.transferFrom(from, to, sendAmount); + } +} From 56261e546292625ee3da5b38d86be0e2165d25d1 Mon Sep 17 00:00:00 2001 From: alvrs Date: Thu, 26 Oct 2023 15:56:51 +0100 Subject: [PATCH 08/27] wip port solady erc721 test --- packages/world-modules/test/ERC721.t.sol | 1070 +++++++++++++++++----- 1 file changed, 848 insertions(+), 222 deletions(-) diff --git a/packages/world-modules/test/ERC721.t.sol b/packages/world-modules/test/ERC721.t.sol index 5e31cc71b6..f5234114f9 100644 --- a/packages/world-modules/test/ERC721.t.sol +++ b/packages/world-modules/test/ERC721.t.sol @@ -1,329 +1,955 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.21; +pragma solidity ^0.8.4; -import { Test } from "forge-std/Test.sol"; -import { console } from "forge-std/console.sol"; +import "./utils/SoladyTest.sol"; -import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; -import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol"; -import { World } from "@latticexyz/world/src/World.sol"; -import { WorldResourceIdLib } from "@latticexyz/world/src/WorldResourceId.sol"; -import { CoreModule } from "@latticexyz/world/src/modules/core/CoreModule.sol"; -import { IBaseWorld } from "@latticexyz/world/src/codegen/interfaces/IBaseWorld.sol"; -import { NamespaceOwner } from "@latticexyz/world/src/codegen/tables/NamespaceOwner.sol"; -import { GasReporter } from "@latticexyz/gas-report/src/GasReporter.sol"; +import { ERC721, MockERC721 } from "./utils/mocks/MockERC721.sol"; -import { PuppetModule } from "../src/modules/puppet/PuppetModule.sol"; +abstract contract ERC721TokenReceiver { + function onERC721Received(address, address, uint256, bytes calldata) external virtual returns (bytes4) { + return ERC721TokenReceiver.onERC721Received.selector; + } +} -import { ERC20Module } from "../src/modules/erc20-puppet/ERC20Module.sol"; -import { ERC20MetadataData } from "../src/modules/erc20-puppet/tables/ERC20Metadata.sol"; -import { ERC20Registry } from "../src/modules/erc20-puppet/tables/ERC20Registry.sol"; -import { ERC20_REGISTRY_TABLE_ID } from "../src/modules/erc20-puppet/constants.sol"; -import { IERC20Mintable } from "../src/modules/erc20-puppet/IERC20Mintable.sol"; -import { registerERC20 } from "../src/modules/erc20-puppet/registerERC20.sol"; -import { IERC20Errors } from "../src/modules/erc20-puppet/IERC20Errors.sol"; +contract ERC721Recipient is ERC721TokenReceiver { + address public operator; + address public from; + uint256 public id; + bytes public data; + + function onERC721Received( + address _operator, + address _from, + uint256 _id, + bytes calldata _data + ) public virtual override returns (bytes4) { + operator = _operator; + from = _from; + id = _id; + data = _data; + + return ERC721TokenReceiver.onERC721Received.selector; + } +} -import { IERC20Events } from "./IERC20Events.sol"; +contract RevertingERC721Recipient is ERC721TokenReceiver { + function onERC721Received(address, address, uint256, bytes calldata) public virtual override returns (bytes4) { + revert(string(abi.encodePacked(ERC721TokenReceiver.onERC721Received.selector))); + } +} -contract ERC20Test is Test, GasReporter, IERC20Events, IERC20Errors { - IBaseWorld world; - ERC20Module erc20Module; - IERC20Mintable token; +contract WrongReturnDataERC721Recipient is ERC721TokenReceiver { + function onERC721Received(address, address, uint256, bytes calldata) public virtual override returns (bytes4) { + return 0xCAFEBEEF; + } +} - function setUp() public { - world = IBaseWorld(address(new World())); - world.initialize(new CoreModule()); - world.installModule(new PuppetModule(), new bytes(0)); - StoreSwitch.setStoreAddress(address(world)); +contract NonERC721Recipient {} + +contract MockERC721WithHooks is MockERC721 { + uint256 public beforeCounter; + uint256 public afterCounter; + + function _beforeTokenTransfer(address, address, uint256) internal virtual override { + beforeCounter++; + } + + function _afterTokenTransfer(address, address, uint256) internal virtual override { + afterCounter++; + } +} - // Register a new ERC20 token - token = registerERC20(world, "myERC20", ERC20MetadataData({ decimals: 18, name: "Token", symbol: "TKN" })); +contract ERC721HooksTest is SoladyTest, ERC721TokenReceiver { + uint256 public expectedBeforeCounter; + uint256 public expectedAfterCounter; + uint256 public ticker; + + function _checkCounters() internal view { + require(expectedBeforeCounter == MockERC721WithHooks(msg.sender).beforeCounter(), "Before counter mismatch."); + require(expectedAfterCounter == MockERC721WithHooks(msg.sender).afterCounter(), "After counter mismatch."); } - function testSetUp() public { - assertTrue(address(token) != address(0)); - assertEq(NamespaceOwner.get(WorldResourceIdLib.encodeNamespace("myERC20")), address(this)); + function onERC721Received(address, address, uint256, bytes calldata) external virtual override returns (bytes4) { + _checkCounters(); + return ERC721TokenReceiver.onERC721Received.selector; } - function testInstallTwice() public { - // Install the ERC20 module - IERC20Mintable anotherToken = registerERC20( - world, - "anotherERC20", - ERC20MetadataData({ decimals: 18, name: "Token", symbol: "TKN" }) - ); - assertTrue(address(anotherToken) != address(0)); - assertTrue(address(anotherToken) != address(token)); + function _testHooks(MockERC721WithHooks token) internal { + address from = _randomNonZeroAddress(); + uint256 tokenId = uint256(keccak256(abi.encode(expectedBeforeCounter, expectedAfterCounter))); + expectedBeforeCounter++; + expectedAfterCounter++; + token.mint(address(this), tokenId); + + expectedBeforeCounter++; + expectedAfterCounter++; + token.transferFrom(address(this), from, tokenId); + + expectedBeforeCounter++; + expectedAfterCounter++; + uint256 r = ticker < 4 ? ticker : _random() % 4; + vm.prank(from); + if (r == 0) { + token.safeTransferFrom(from, address(this), tokenId); + } else if (r == 1) { + token.safeTransferFrom(from, address(this), tokenId, ""); + } else if (r == 2) { + token.directSafeTransferFrom(from, address(this), tokenId); + } else if (r == 3) { + token.directSafeTransferFrom(from, address(this), tokenId, ""); + } else { + revert(); + } } - ///////////////////////////////////////////////// - // SOLADY ERC20 TEST CAES - // (https://github.com/Vectorized/solady/blob/main/test/ERC20.t.sol) - ///////////////////////////////////////////////// + function testERC721Hooks() public { + MockERC721WithHooks token = new MockERC721WithHooks(); - function testMetadata() public { - assertEq(token.name(), "Token"); - assertEq(token.symbol(), "TKN"); - assertEq(token.decimals(), 18); + for (uint256 i; i < 32; ++i) { + _testHooks(token); + } } +} - function testMint() public { - vm.expectEmit(true, true, true, true); - emit Transfer(address(0), address(0xBEEF), 1e18); - startGasReport("mint"); - token.mint(address(0xBEEF), 1e18); - endGasReport(); +contract ERC721Test is SoladyTest { + MockERC721 token; + + uint256 private constant _ERC721_MASTER_SLOT_SEED = 0x7d8825530a5a2e7a << 192; + + event Transfer(address indexed from, address indexed to, uint256 indexed id); - assertEq(token.totalSupply(), 1e18); - assertEq(token.balanceOf(address(0xBEEF)), 1e18); + event Approval(address indexed owner, address indexed approved, uint256 indexed id); + + event ApprovalForAll(address indexed owner, address indexed operator, bool approved); + + function setUp() public { + token = new MockERC721(); } - function testBurn() public { - token.mint(address(0xBEEF), 1e18); + function _expectMintEvent(address to, uint256 id) internal { + _expectTransferEvent(address(0), to, id); + } + function _expectBurnEvent(address from, uint256 id) internal { + _expectTransferEvent(from, address(0), id); + } + + function _expectTransferEvent(address from, address to, uint256 id) internal { vm.expectEmit(true, true, true, true); - emit Transfer(address(0xBEEF), address(0), 0.9e18); - startGasReport("burn"); - token.burn(address(0xBEEF), 0.9e18); - endGasReport(); + emit Transfer(from, to, id); + } - assertEq(token.totalSupply(), 1e18 - 0.9e18); - assertEq(token.balanceOf(address(0xBEEF)), 0.1e18); + function _expectApprovalEvent(address owner, address approved, uint256 id) internal { + vm.expectEmit(true, true, true, true); + emit Approval(owner, approved, id); } - function testApprove() public { + function _expectApprovalForAllEvent(address owner, address operator, bool approved) internal { vm.expectEmit(true, true, true, true); - emit Approval(address(this), address(0xBEEF), 1e18); - startGasReport("approve"); - bool success = token.approve(address(0xBEEF), 1e18); - endGasReport(); - assertTrue(success); + emit ApprovalForAll(owner, operator, approved); + } - assertEq(token.allowance(address(this), address(0xBEEF)), 1e18); + function _aux(address owner) internal pure returns (uint224 result) { + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, owner) + result := shr(32, shl(32, keccak256(0x0c, 0x14))) + } } - function testTransfer() public { - token.mint(address(this), 1e18); + function _extraData(uint256 id) internal pure returns (uint96 result) { + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, id) + result := shr(160, shl(160, keccak256(0x00, 0x20))) + } + } - vm.expectEmit(true, true, true, true); - emit Transfer(address(this), address(0xBEEF), 1e18); - startGasReport("transfer"); - bool success = token.transfer(address(0xBEEF), 1e18); - endGasReport(); - assertTrue(success); - assertEq(token.totalSupply(), 1e18); + function _transferFrom(address from, address to, uint256 id) internal { + if (_random() % 2 == 0) { + token.transferFrom(from, to, id); + } else { + token.directTransferFrom(from, to, id); + } + } - assertEq(token.balanceOf(address(this)), 0); - assertEq(token.balanceOf(address(0xBEEF)), 1e18); + function _safeTransferFrom(address from, address to, uint256 id) internal { + if (_random() % 2 == 0) { + token.safeTransferFrom(from, to, id); + } else { + token.directSafeTransferFrom(from, to, id); + } + } + + function _safeTransferFrom(address from, address to, uint256 id, bytes memory data) internal { + if (_random() % 2 == 0) { + token.safeTransferFrom(from, to, id, data); + } else { + token.directSafeTransferFrom(from, to, id, data); + } + } + + function _approve(address spender, uint256 id) internal { + if (_random() % 2 == 0) { + token.approve(spender, id); + } else { + token.directApprove(spender, id); + } + } + + function _setApprovalForAll(address operator, bool approved) internal { + if (_random() % 2 == 0) { + token.setApprovalForAll(operator, approved); + } else { + token.directSetApprovalForAll(operator, approved); + } + } + + function _ownerOf(uint256 id) internal returns (address) { + if (_random() % 2 == 0) { + return token.ownerOf(id); + } else { + return token.directOwnerOf(id); + } + } + + function _getApproved(uint256 id) internal returns (address) { + if (_random() % 2 == 0) { + return token.getApproved(id); + } else { + return token.directGetApproved(id); + } + } + + function _owners() internal returns (address a, address b) { + a = _randomNonZeroAddress(); + b = _randomNonZeroAddress(); + while (a == b) b = _randomNonZeroAddress(); + } + + function testSafetyOfCustomStorage(uint256 id0, uint256 id1) public { + bool safe; + while (id0 == id1) id1 = _random(); + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, id0) + mstore(0x1c, _ERC721_MASTER_SLOT_SEED) + let slot0 := add(id0, add(id0, keccak256(0x00, 0x20))) + let slot2 := add(1, slot0) + mstore(0x00, id1) + mstore(0x1c, _ERC721_MASTER_SLOT_SEED) + let slot1 := add(id1, add(id1, keccak256(0x00, 0x20))) + let slot3 := add(1, slot1) + safe := 1 + if eq(slot0, slot1) { + safe := 0 + } + if eq(slot0, slot2) { + safe := 0 + } + if eq(slot0, slot3) { + safe := 0 + } + if eq(slot1, slot2) { + safe := 0 + } + if eq(slot1, slot3) { + safe := 0 + } + if eq(slot2, slot3) { + safe := 0 + } + } + require(safe, "Custom storage not safe"); + } + + function testAuthorizedEquivalence(address by, bool isOwnerOrOperator, bool isApprovedAccount) public { + bool a = true; + bool b = true; + /// @solidity memory-safe-assembly + assembly { + if by { + if iszero(isOwnerOrOperator) { + a := isApprovedAccount + } + } + if iszero(or(iszero(by), isOwnerOrOperator)) { + b := isApprovedAccount + } + } + assertEq(a, b); + } + + function testCannotExceedMaxBalance() public { + bytes32 balanceSlot; + (address owner0, address owner1) = _owners(); + + /// @solidity memory-safe-assembly + assembly { + mstore(0x1c, _ERC721_MASTER_SLOT_SEED) + mstore(0x00, owner0) + balanceSlot := keccak256(0x0c, 0x1c) + } + + vm.store(address(token), balanceSlot, bytes32(uint256(0xfffffffe))); + token.setAux(owner0, type(uint224).max); + assertEq(token.balanceOf(owner0), 0xfffffffe); + assertEq(token.getAux(owner0), type(uint224).max); + token.mint(owner0, 0); + assertEq(token.balanceOf(owner0), 0xffffffff); + + vm.expectRevert(ERC721.AccountBalanceOverflow.selector); + token.mint(owner0, 1); + + token.uncheckedBurn(0); + assertEq(token.balanceOf(owner0), 0xfffffffe); + + token.mint(owner1, 0); + vm.prank(owner1); + _transferFrom(owner1, owner0, 0); + + token.mint(owner1, 1); + vm.expectRevert(ERC721.AccountBalanceOverflow.selector); + vm.prank(owner1); + _transferFrom(owner1, owner0, 1); + assertEq(token.getAux(owner0), type(uint224).max); + } + + function testMint(uint256 id) public { + address owner = _randomNonZeroAddress(); + + _expectMintEvent(owner, id); + token.mint(owner, id); + + assertEq(token.balanceOf(owner), 1); + assertEq(_ownerOf(id), owner); + } + + function testBurn(uint256 id) public { + address owner = _randomNonZeroAddress(); + + _expectMintEvent(owner, id); + token.mint(owner, id); + + if (_random() % 2 == 0) { + _expectBurnEvent(owner, id); + token.uncheckedBurn(id); + } else { + vm.expectRevert(ERC721.NotOwnerNorApproved.selector); + token.burn(id); + uint256 r = _random() % 3; + if (r == 0) { + vm.prank(owner); + _transferFrom(owner, address(this), id); + _expectBurnEvent(address(this), id); + token.burn(id); + } + if (r == 1) { + vm.prank(owner); + _setApprovalForAll(address(this), true); + _expectBurnEvent(owner, id); + token.burn(id); + } + if (r == 2) { + vm.prank(owner); + _approve(address(this), id); + _expectBurnEvent(owner, id); + token.burn(id); + } + } + + assertEq(token.balanceOf(owner), 0); + + vm.expectRevert(ERC721.TokenDoesNotExist.selector); + _ownerOf(id); } function testTransferFrom() public { - address from = address(0xABCD); + address owner = _randomNonZeroAddress(); + token.mint(owner, 0); + vm.prank(owner); + token.transferFrom(owner, address(this), 0); + } - token.mint(from, 1e18); + function testEverything(uint256) public { + address[2] memory owners; + uint256[][2] memory tokens; + + unchecked { + (owners[0], owners[1]) = _owners(); + for (uint256 j; j != 2; ++j) { + tokens[j] = new uint256[](_random() % 3); + } + + for (uint256 j; j != 2; ++j) { + token.setAux(owners[j], _aux(owners[j])); + for (uint256 i; i != tokens[j].length; ) { + uint256 id = _random(); + if (!token.exists(id)) { + tokens[j][i++] = id; + _expectMintEvent(owners[j], id); + token.mint(owners[j], id); + token.setExtraData(id, _extraData(id)); + } + } + } + for (uint256 j; j != 2; ++j) { + assertEq(token.balanceOf(owners[j]), tokens[j].length); + for (uint256 i; i != tokens[j].length; ++i) { + vm.prank(owners[j]); + _expectApprovalEvent(owners[j], address(this), tokens[j][i]); + _approve(address(this), tokens[j][i]); + } + } + for (uint256 j; j != 2; ++j) { + for (uint256 i; i != tokens[j].length; ++i) { + assertEq(_getApproved(tokens[j][i]), address(this)); + uint256 fromBalanceBefore = token.balanceOf(owners[j]); + uint256 toBalanceBefore = token.balanceOf(owners[j ^ 1]); + _expectTransferEvent(owners[j], owners[j ^ 1], tokens[j][i]); + _transferFrom(owners[j], owners[j ^ 1], tokens[j][i]); + assertEq(token.balanceOf(owners[j]), fromBalanceBefore - 1); + assertEq(token.balanceOf(owners[j ^ 1]), toBalanceBefore + 1); + assertEq(_getApproved(tokens[j][i]), address(0)); + } + } + for (uint256 j; j != 2; ++j) { + for (uint256 i; i != tokens[j].length; ++i) { + assertEq(_ownerOf(tokens[j][i]), owners[j ^ 1]); + assertEq(token.getExtraData(tokens[j][i]), _extraData(tokens[j][i])); + } + } + if (_random() % 2 == 0) { + for (uint256 j; j != 2; ++j) { + for (uint256 i; i != tokens[j].length; ++i) { + vm.expectRevert(ERC721.NotOwnerNorApproved.selector); + _transferFrom(owners[j ^ 1], owners[j], tokens[j][i]); + vm.prank(owners[j ^ 1]); + _expectApprovalEvent(owners[j ^ 1], address(this), tokens[j][i]); + _approve(address(this), tokens[j][i]); + _expectTransferEvent(owners[j ^ 1], owners[j], tokens[j][i]); + _transferFrom(owners[j ^ 1], owners[j], tokens[j][i]); + } + } + } else { + for (uint256 j; j != 2; ++j) { + vm.prank(owners[j ^ 1]); + _expectApprovalForAllEvent(owners[j ^ 1], address(this), true); + token.setApprovalForAll(address(this), true); + for (uint256 i; i != tokens[j].length; ++i) { + _expectTransferEvent(owners[j ^ 1], owners[j], tokens[j][i]); + _transferFrom(owners[j ^ 1], owners[j], tokens[j][i]); + } + } + } + for (uint256 j; j != 2; ++j) { + assertEq(token.getAux(owners[j]), _aux(owners[j])); + for (uint256 i; i != tokens[j].length; ++i) { + assertEq(_ownerOf(tokens[j][i]), owners[j]); + assertEq(token.getExtraData(tokens[j][i]), _extraData(tokens[j][i])); + } + } + for (uint256 j; j != 2; ++j) { + for (uint256 i; i != tokens[j].length; ++i) { + token.uncheckedBurn(tokens[j][i]); + } + } + for (uint256 j; j != 2; ++j) { + assertEq(token.balanceOf(owners[j]), 0); + for (uint256 i; i != tokens[j].length; ++i) { + assertEq(token.getExtraData(tokens[j][i]), _extraData(tokens[j][i])); + } + } + } + } - vm.prank(from); - token.approve(address(this), 1e18); + function testIsApprovedOrOwner(uint256 id) public { + (address owner0, address owner1) = _owners(); - vm.expectEmit(true, true, true, true); - emit Transfer(from, address(0xBEEF), 1e18); - startGasReport("transferFrom"); - bool success = token.transferFrom(from, address(0xBEEF), 1e18); - endGasReport(); - assertTrue(success); - assertEq(token.totalSupply(), 1e18); + vm.expectRevert(ERC721.TokenDoesNotExist.selector); + token.isApprovedOrOwner(owner0, id); - assertEq(token.allowance(from, address(this)), 0); + token.mint(owner0, id); + assertEq(token.isApprovedOrOwner(owner0, id), true); - assertEq(token.balanceOf(from), 0); - assertEq(token.balanceOf(address(0xBEEF)), 1e18); + vm.prank(owner0); + _transferFrom(owner0, owner1, id); + assertEq(token.isApprovedOrOwner(owner0, id), false); + + vm.prank(owner1); + _setApprovalForAll(owner0, true); + assertEq(token.isApprovedOrOwner(owner0, id), true); + + vm.prank(owner1); + _setApprovalForAll(owner0, false); + assertEq(token.isApprovedOrOwner(owner0, id), false); + + vm.prank(owner1); + _approve(owner0, id); + assertEq(token.isApprovedOrOwner(owner0, id), true); } - function testInfiniteApproveTransferFrom() public { - address from = address(0xABCD); + function testExtraData(uint256 id) public { + (address owner0, address owner1) = _owners(); - token.mint(from, 1e18); + bool setExtraData = _random() % 2 == 0; + uint96 extraData = uint96(_bound(_random(), 0, type(uint96).max)); + if (setExtraData) { + token.setExtraData(id, extraData); + } + _expectMintEvent(owner0, id); + token.mint(owner0, id); + if (setExtraData) { + assertEq(token.getExtraData(id), extraData); + } else { + assertEq(token.getExtraData(id), 0); + } - vm.prank(from); - token.approve(address(this), type(uint256).max); + vm.prank(owner0); + _expectTransferEvent(owner0, owner1, id); + _transferFrom(owner0, owner1, id); + if (setExtraData) { + assertEq(token.getExtraData(id), extraData); + } else { + assertEq(token.getExtraData(id), 0); + } + assertEq(_ownerOf(id), owner1); + + if (_random() % 2 == 0) { + extraData = uint96(_bound(_random(), 0, type(uint96).max)); + token.setExtraData(id, extraData); + setExtraData = true; + } + + _expectBurnEvent(owner1, id); + token.uncheckedBurn(id); + if (setExtraData) { + assertEq(token.getExtraData(id), extraData); + } else { + assertEq(token.getExtraData(id), 0); + } + vm.expectRevert(ERC721.TokenDoesNotExist.selector); + _ownerOf(id); + } + + function testExtraData2(uint256 id0, uint256 id1) public { + while (id0 == id1) id1 = _random(); + token.setExtraData(id0, _extraData(id0)); + token.setExtraData(id1, _extraData(id1)); + assertEq(token.getExtraData(id0), _extraData(id0)); + assertEq(token.getExtraData(id1), _extraData(id1)); + } + + function testAux(uint256) public { + (address owner0, address owner1) = _owners(); + + bool setAux = _random() % 2 == 0; + if (setAux) { + token.setAux(owner0, _aux(owner0)); + token.setAux(owner1, _aux(owner1)); + } + + for (uint256 i; i < 2; ++i) { + _expectMintEvent(owner0, i * 2 + 0); + token.mint(owner0, i * 2 + 0); + assertEq(token.balanceOf(owner0), i + 1); + + _expectMintEvent(owner1, i * 2 + 1); + token.mint(owner1, i * 2 + 1); + assertEq(token.balanceOf(owner1), i + 1); + + if (setAux) { + assertEq(token.getAux(owner0), _aux(owner0)); + assertEq(token.getAux(owner1), _aux(owner1)); + } else { + assertEq(token.getAux(owner0), 0); + assertEq(token.getAux(owner1), 0); + } + } + + for (uint256 i; i < 2; ++i) { + _expectBurnEvent(owner0, i * 2 + 0); + token.uncheckedBurn(i * 2 + 0); + assertEq(token.balanceOf(owner0), 1 - i); + + _expectBurnEvent(owner1, i * 2 + 1); + token.uncheckedBurn(i * 2 + 1); + assertEq(token.balanceOf(owner1), 1 - i); + + if (setAux) { + assertEq(token.getAux(owner0), _aux(owner0)); + assertEq(token.getAux(owner1), _aux(owner1)); + } else { + assertEq(token.getAux(owner0), 0); + assertEq(token.getAux(owner1), 0); + } + } + } + + function testApprove(uint256 id) public { + (address spender, ) = _randomSigner(); + + token.mint(address(this), id); + + _expectApprovalEvent(address(this), spender, id); + _approve(spender, id); + assertEq(_getApproved(id), spender); + } + + function testApproveBurn(uint256 id) public { + (address spender, ) = _randomSigner(); - assertTrue(token.transferFrom(from, address(0xBEEF), 1e18)); - assertEq(token.totalSupply(), 1e18); + token.mint(address(this), id); - assertEq(token.allowance(from, address(this)), type(uint256).max); + _approve(spender, id); + token.uncheckedBurn(id); + + assertEq(token.balanceOf(address(this)), 0); + + vm.expectRevert(ERC721.TokenDoesNotExist.selector); + _getApproved(id); + + vm.expectRevert(ERC721.TokenDoesNotExist.selector); + _ownerOf(id); + } + + function testApproveAll(uint256) public { + (address operator, ) = _randomSigner(); + bool approved = _random() % 2 == 0; + _expectApprovalForAllEvent(address(this), operator, approved); + _setApprovalForAll(operator, approved); + assertEq(token.isApprovedForAll(address(this), operator), approved); + } + + function testTransferFrom(uint256 id) public { + (address from, address to) = _owners(); + + token.mint(from, id); + + if (_random() % 2 == 0) { + uint256 r = _random() % 3; + if (r == 0) { + vm.prank(from); + _approve(address(this), id); + _expectTransferEvent(from, to, id); + _transferFrom(from, to, id); + } + if (r == 1) { + vm.prank(from); + _setApprovalForAll(address(this), true); + _expectTransferEvent(from, to, id); + _transferFrom(from, to, id); + } + if (r == 2) { + vm.prank(from); + _expectTransferEvent(from, address(this), id); + _transferFrom(from, address(this), id); + _expectTransferEvent(address(this), to, id); + _transferFrom(address(this), to, id); + } + } else { + (address temp, ) = _randomSigner(); + while (temp == from || temp == to) (temp, ) = _randomSigner(); + if (_random() % 2 == 0) { + _expectTransferEvent(from, temp, id); + token.uncheckedTransferFrom(from, temp, id); + } else { + vm.prank(from); + _expectTransferEvent(from, temp, id); + _transferFrom(from, temp, id); + } + _expectTransferEvent(temp, to, id); + token.uncheckedTransferFrom(temp, to, id); + } + + assertEq(_getApproved(id), address(0)); + assertEq(_ownerOf(id), to); + assertEq(token.balanceOf(to), 1); assertEq(token.balanceOf(from), 0); - assertEq(token.balanceOf(address(0xBEEF)), 1e18); } - function testMintOverMaxUintReverts() public { - token.mint(address(this), type(uint256).max); - vm.expectRevert(); - token.mint(address(this), 1); + function testTransferFromSelf(uint256 id) public { + (address to, ) = _randomSigner(); + + token.mint(address(this), id); + + _transferFrom(address(this), to, id); + + assertEq(_getApproved(id), address(0)); + assertEq(_ownerOf(id), to); + assertEq(token.balanceOf(to), 1); + assertEq(token.balanceOf(address(this)), 0); } - function testTransferInsufficientBalanceReverts() public { - token.mint(address(this), 0.9e18); - vm.expectRevert( - abi.encodeWithSelector(IERC20Errors.ERC20InsufficientBalance.selector, address(this), 0.9e18, 1e18) - ); - token.transfer(address(0xBEEF), 1e18); + function testTransferFromApproveAll(uint256 id) public { + (address from, address to) = _owners(); + + token.mint(from, id); + + vm.prank(from); + _setApprovalForAll(address(this), true); + + _transferFrom(from, to, id); + + assertEq(_getApproved(id), address(0)); + assertEq(_ownerOf(id), to); + assertEq(token.balanceOf(to), 1); + assertEq(token.balanceOf(from), 0); } - function testTransferFromInsufficientAllowanceReverts() public { - address from = address(0xABCD); + function testSafeTransferFromToEOA(uint256 id) public { + (address from, address to) = _owners(); - token.mint(from, 1e18); + token.mint(from, id); vm.prank(from); - token.approve(address(this), 0.9e18); + _setApprovalForAll(address(this), true); + + _safeTransferFrom(from, to, id); - vm.expectRevert(abi.encodeWithSelector(ERC20InsufficientAllowance.selector, address(this), 0.9e18, 1e18)); - token.transferFrom(from, address(0xBEEF), 1e18); + assertEq(_getApproved(id), address(0)); + assertEq(_ownerOf(id), to); + assertEq(token.balanceOf(to), 1); + assertEq(token.balanceOf(from), 0); } - function testTransferFromInsufficientBalanceReverts() public { - address from = address(0xABCD); + function testSafeTransferFromToERC721Recipient(uint256 id) public { + (address from, ) = _randomSigner(); - token.mint(from, 0.9e18); + ERC721Recipient recipient = new ERC721Recipient(); + + token.mint(from, id); vm.prank(from); - token.approve(address(this), 1e18); + _setApprovalForAll(address(this), true); + + _safeTransferFrom(from, address(recipient), id); - vm.expectRevert(abi.encodeWithSelector(ERC20InsufficientBalance.selector, from, 0.9e18, 1e18)); - token.transferFrom(from, address(0xBEEF), 1e18); + assertEq(_getApproved(id), address(0)); + assertEq(_ownerOf(id), address(recipient)); + assertEq(token.balanceOf(address(recipient)), 1); + assertEq(token.balanceOf(from), 0); + + assertEq(recipient.operator(), address(this)); + assertEq(recipient.from(), from); + assertEq(recipient.id(), id); + assertEq(recipient.data(), ""); } - function testMint(address to, uint256 amount) public { - vm.assume(to != address(0)); + function testSafeTransferFromToERC721RecipientWithData(uint256 id, bytes memory data) public { + (address from, ) = _randomSigner(); - vm.expectEmit(true, true, true, true); - emit Transfer(address(0), to, amount); - token.mint(to, amount); + ERC721Recipient recipient = new ERC721Recipient(); + + token.mint(from, id); + + vm.prank(from); + _setApprovalForAll(address(this), true); + + _safeTransferFrom(from, address(recipient), id, data); + + assertEq(recipient.data(), data); + assertEq(recipient.id(), id); + assertEq(recipient.operator(), address(this)); + assertEq(recipient.from(), from); - assertEq(token.totalSupply(), amount); - assertEq(token.balanceOf(to), amount); + assertEq(_getApproved(id), address(0)); + assertEq(_ownerOf(id), address(recipient)); + assertEq(token.balanceOf(address(recipient)), 1); + assertEq(token.balanceOf(from), 0); } - function testBurn(address from, uint256 mintAmount, uint256 burnAmount) public { - vm.assume(from != address(0)); - vm.assume(burnAmount <= mintAmount); + function testSafeMintToEOA(uint256 id) public { + (address to, ) = _randomSigner(); - token.mint(from, mintAmount); - vm.expectEmit(true, true, true, true); - emit Transfer(from, address(0), burnAmount); - token.burn(from, burnAmount); + token.safeMint(to, id); - assertEq(token.totalSupply(), mintAmount - burnAmount); - assertEq(token.balanceOf(from), mintAmount - burnAmount); + assertEq(_ownerOf(id), address(to)); + assertEq(token.balanceOf(address(to)), 1); } - function testApprove(address to, uint256 amount) public { - vm.assume(to != address(0)); + function testSafeMintToERC721Recipient(uint256 id) public { + ERC721Recipient to = new ERC721Recipient(); + + token.safeMint(address(to), id); - assertTrue(token.approve(to, amount)); + assertEq(_ownerOf(id), address(to)); + assertEq(token.balanceOf(address(to)), 1); - assertEq(token.allowance(address(this), to), amount); + assertEq(to.operator(), address(this)); + assertEq(to.from(), address(0)); + assertEq(to.id(), id); + assertEq(to.data(), ""); } - function testTransfer(address to, uint256 amount) public { - vm.assume(to != address(0)); - token.mint(address(this), amount); + function testSafeMintToERC721RecipientWithData(uint256 id, bytes memory data) public { + ERC721Recipient to = new ERC721Recipient(); - vm.expectEmit(true, true, true, true); - emit Transfer(address(this), to, amount); - assertTrue(token.transfer(to, amount)); - assertEq(token.totalSupply(), amount); + token.safeMint(address(to), id, data); - if (address(this) == to) { - assertEq(token.balanceOf(address(this)), amount); - } else { - assertEq(token.balanceOf(address(this)), 0); - assertEq(token.balanceOf(to), amount); - } + assertEq(_ownerOf(id), address(to)); + assertEq(token.balanceOf(address(to)), 1); + + assertEq(to.operator(), address(this)); + assertEq(to.from(), address(0)); + assertEq(to.id(), id); + assertEq(to.data(), data); } - function testTransferFrom(address spender, address from, address to, uint256 approval, uint256 amount) public { - vm.assume(from != address(0)); - vm.assume(to != address(0)); - vm.assume(spender != address(0)); - vm.assume(amount <= approval); + function testMintToZeroReverts(uint256 id) public { + vm.expectRevert(ERC721.TransferToZeroAddress.selector); + token.mint(address(0), id); + } - token.mint(from, amount); - assertEq(token.balanceOf(from), amount); + function testDoubleMintReverts(uint256 id) public { + (address to, ) = _randomSigner(); - vm.prank(from); - token.approve(spender, approval); + token.mint(to, id); + vm.expectRevert(ERC721.TokenAlreadyExists.selector); + token.mint(to, id); + } - vm.expectEmit(true, true, true, true); - emit Transfer(from, to, amount); - vm.prank(spender); - assertTrue(token.transferFrom(from, to, amount)); - assertEq(token.totalSupply(), amount); + function testBurnNonExistentReverts(uint256 id) public { + vm.expectRevert(ERC721.TokenDoesNotExist.selector); + token.uncheckedBurn(id); + } - if (approval == type(uint256).max) { - assertEq(token.allowance(from, spender), approval); - } else { - assertEq(token.allowance(from, spender), approval - amount); - } + function testDoubleBurnReverts(uint256 id) public { + (address to, ) = _randomSigner(); - if (from == to) { - assertEq(token.balanceOf(from), amount); - } else { - assertEq(token.balanceOf(from), 0); - assertEq(token.balanceOf(to), amount); - } + token.mint(to, id); + + token.uncheckedBurn(id); + vm.expectRevert(ERC721.TokenDoesNotExist.selector); + token.uncheckedBurn(id); + } + + function testApproveNonExistentReverts(uint256 id, address to) public { + vm.expectRevert(ERC721.TokenDoesNotExist.selector); + _approve(to, id); } - function testBurnInsufficientBalanceReverts(address to, uint256 mintAmount, uint256 burnAmount) public { - vm.assume(to != address(0)); - vm.assume(mintAmount < type(uint256).max); - vm.assume(burnAmount > mintAmount); + function testApproveUnauthorizedReverts(uint256 id) public { + (address owner, address to) = _owners(); - token.mint(to, mintAmount); - vm.expectRevert(abi.encodeWithSelector(ERC20InsufficientBalance.selector, to, mintAmount, burnAmount)); - token.burn(to, burnAmount); + token.mint(owner, id); + vm.expectRevert(ERC721.NotOwnerNorApproved.selector); + _approve(to, id); } - function testTransferInsufficientBalanceReverts(address to, uint256 mintAmount, uint256 sendAmount) public { - vm.assume(to != address(0)); - vm.assume(mintAmount < type(uint256).max); - vm.assume(sendAmount > mintAmount); + function testTransferFromNotExistentReverts(address from, address to, uint256 id) public { + vm.expectRevert(ERC721.TokenDoesNotExist.selector); + _transferFrom(from, to, id); + } + + function testTransferFromWrongFromReverts(address to, uint256 id) public { + (address owner, address from) = _owners(); - token.mint(address(this), mintAmount); - vm.expectRevert(abi.encodeWithSelector(ERC20InsufficientBalance.selector, address(this), mintAmount, sendAmount)); - token.transfer(to, sendAmount); + token.mint(owner, id); + vm.expectRevert(ERC721.TransferFromIncorrectOwner.selector); + _transferFrom(from, to, id); } - function testTransferFromInsufficientAllowanceReverts(address to, uint256 approval, uint256 amount) public { - vm.assume(to != address(0)); - vm.assume(approval < type(uint256).max); - vm.assume(amount > approval); + function testTransferFromToZeroReverts(uint256 id) public { + token.mint(address(this), id); - address from = address(0xABCD); + vm.expectRevert(ERC721.TransferToZeroAddress.selector); + _transferFrom(address(this), address(0), id); + } - token.mint(from, amount); + function testTransferFromNotOwner(uint256 id) public { + (address from, address to) = _owners(); - vm.prank(from); - token.approve(address(this), approval); + token.mint(from, id); - vm.expectRevert(abi.encodeWithSelector(ERC20InsufficientAllowance.selector, address(this), approval, amount)); - token.transferFrom(from, to, amount); + vm.expectRevert(ERC721.NotOwnerNorApproved.selector); + _transferFrom(from, to, id); } - function testTransferFromInsufficientBalanceReverts(address to, uint256 mintAmount, uint256 sendAmount) public { - vm.assume(to != address(0)); - vm.assume(mintAmount < type(uint256).max); - vm.assume(sendAmount > mintAmount); + function testSafeTransferFromToNonERC721RecipientReverts(uint256 id) public { + token.mint(address(this), id); + address to = address(new NonERC721Recipient()); + vm.expectRevert(ERC721.TransferToNonERC721ReceiverImplementer.selector); + _safeTransferFrom(address(this), address(to), id); + } - address from = address(0xABCD); + function testSafeTransferFromToNonERC721RecipientWithDataReverts(uint256 id, bytes memory data) public { + token.mint(address(this), id); + address to = address(new NonERC721Recipient()); + vm.expectRevert(ERC721.TransferToNonERC721ReceiverImplementer.selector); + _safeTransferFrom(address(this), to, id, data); + } - token.mint(from, mintAmount); + function testSafeTransferFromToRevertingERC721RecipientReverts(uint256 id) public { + token.mint(address(this), id); + address to = address(new RevertingERC721Recipient()); + vm.expectRevert(abi.encodePacked(ERC721TokenReceiver.onERC721Received.selector)); + _safeTransferFrom(address(this), to, id); + } - vm.prank(from); - token.approve(address(this), sendAmount); + function testSafeTransferFromToRevertingERC721RecipientWithDataReverts(uint256 id, bytes memory data) public { + token.mint(address(this), id); + address to = address(new RevertingERC721Recipient()); + vm.expectRevert(abi.encodePacked(ERC721TokenReceiver.onERC721Received.selector)); + _safeTransferFrom(address(this), to, id, data); + } + + function testSafeTransferFromToERC721RecipientWithWrongReturnDataReverts(uint256 id) public { + token.mint(address(this), id); + address to = address(new WrongReturnDataERC721Recipient()); + vm.expectRevert(ERC721.TransferToNonERC721ReceiverImplementer.selector); + _safeTransferFrom(address(this), to, id); + } + + function testSafeTransferFromToERC721RecipientWithWrongReturnDataWithDataReverts( + uint256 id, + bytes memory data + ) public { + token.mint(address(this), id); + address to = address(new WrongReturnDataERC721Recipient()); + vm.expectRevert(ERC721.TransferToNonERC721ReceiverImplementer.selector); + _safeTransferFrom(address(this), to, id, data); + } + + function testSafeMintToNonERC721RecipientReverts(uint256 id) public { + address to = address(new NonERC721Recipient()); + vm.expectRevert(ERC721.TransferToNonERC721ReceiverImplementer.selector); + token.safeMint(to, id); + } + + function testSafeMintToNonERC721RecipientWithDataReverts(uint256 id, bytes memory data) public { + address to = address(new NonERC721Recipient()); + vm.expectRevert(ERC721.TransferToNonERC721ReceiverImplementer.selector); + token.safeMint(to, id, data); + } + + function testSafeMintToRevertingERC721RecipientReverts(uint256 id) public { + address to = address(new RevertingERC721Recipient()); + vm.expectRevert(abi.encodePacked(ERC721TokenReceiver.onERC721Received.selector)); + token.safeMint(to, id); + } + + function testSafeMintToRevertingERC721RecipientWithDataReverts(uint256 id, bytes memory data) public { + address to = address(new RevertingERC721Recipient()); + vm.expectRevert(abi.encodePacked(ERC721TokenReceiver.onERC721Received.selector)); + token.safeMint(to, id, data); + } + + function testSafeMintToERC721RecipientWithWrongReturnData(uint256 id) public { + address to = address(new WrongReturnDataERC721Recipient()); + vm.expectRevert(ERC721.TransferToNonERC721ReceiverImplementer.selector); + token.safeMint(to, id); + } + + function testSafeMintToERC721RecipientWithWrongReturnDataWithData(uint256 id, bytes memory data) public { + address to = address(new WrongReturnDataERC721Recipient()); + vm.expectRevert(ERC721.TransferToNonERC721ReceiverImplementer.selector); + token.safeMint(to, id, data); + } - vm.expectRevert(abi.encodeWithSelector(ERC20InsufficientBalance.selector, from, mintAmount, sendAmount)); - token.transferFrom(from, to, sendAmount); + function testOwnerOfNonExistent(uint256 id) public { + vm.expectRevert(ERC721.TokenDoesNotExist.selector); + _ownerOf(id); } } From 40a040e5973700f73681d21a3af17763e85e1fd0 Mon Sep 17 00:00:00 2001 From: alvrs Date: Sun, 29 Oct 2023 21:27:07 +0100 Subject: [PATCH 09/27] draft refactor tests --- .../modules/erc721-puppet/ERC721System.sol | 26 + packages/world-modules/test/ERC721.t.sol | 923 +++++------------- 2 files changed, 281 insertions(+), 668 deletions(-) diff --git a/packages/world-modules/src/modules/erc721-puppet/ERC721System.sol b/packages/world-modules/src/modules/erc721-puppet/ERC721System.sol index dbf42d9afe..08653691a1 100644 --- a/packages/world-modules/src/modules/erc721-puppet/ERC721System.sol +++ b/packages/world-modules/src/modules/erc721-puppet/ERC721System.sol @@ -27,6 +27,7 @@ import { _balancesTableId, _metadataTableId, _tokenUriTableId, _operatorApproval /** * TODO: * - Add ERC721 tests + * - TODO: allow burn to be called by owner of token */ contract ERC721System is IERC721Mintable, System, PuppetMaster { @@ -159,6 +160,31 @@ contract ERC721System is IERC721Mintable, System, PuppetMaster { _mint(to, tokenId); } + /** + * @dev Mints `tokenId`, transfers it to `to` and checks for `to` acceptance. + * + * Requirements: + * + * - caller must own the namespace + * - `tokenId` must not exist. + * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. + * + * Emits a {Transfer} event. + */ + function safeMint(address to, uint256 tokenId) public { + _requireOwner(); + _safeMint(to, tokenId, ""); + } + + /** + * @dev Same as {xref-ERC721-safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is + * forwarded in {IERC721Receiver-onERC721Received} to contract recipients. + */ + function safeMint(address to, uint256 tokenId, bytes memory data) public virtual { + _requireOwner(); + _safeMint(to, tokenId, data); + } + /** * @dev Destroys `tokenId`. * The approval is cleared when the token is burned. diff --git a/packages/world-modules/test/ERC721.t.sol b/packages/world-modules/test/ERC721.t.sol index f5234114f9..b3fbe3fc90 100644 --- a/packages/world-modules/test/ERC721.t.sol +++ b/packages/world-modules/test/ERC721.t.sol @@ -1,9 +1,27 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; - -import "./utils/SoladyTest.sol"; - -import { ERC721, MockERC721 } from "./utils/mocks/MockERC721.sol"; +pragma solidity >=0.8.21; + +import { Test } from "forge-std/Test.sol"; +import { console } from "forge-std/console.sol"; + +import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; +import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol"; +import { World } from "@latticexyz/world/src/World.sol"; +import { WorldResourceIdLib, WorldResourceIdInstance } from "@latticexyz/world/src/WorldResourceId.sol"; +import { CoreModule } from "@latticexyz/world/src/modules/core/CoreModule.sol"; +import { IBaseWorld } from "@latticexyz/world/src/codegen/interfaces/IBaseWorld.sol"; +import { NamespaceOwner } from "@latticexyz/world/src/codegen/tables/NamespaceOwner.sol"; +import { IWorldErrors } from "@latticexyz/world/src/IWorldErrors.sol"; +import { GasReporter } from "@latticexyz/gas-report/src/GasReporter.sol"; + +import { PuppetModule } from "../src/modules/puppet/PuppetModule.sol"; +import { ERC721Module } from "../src/modules/erc721-puppet/ERC721Module.sol"; +import { ERC721MetadataData } from "../src/modules/erc721-puppet/tables/ERC721Metadata.sol"; +import { IERC721Mintable } from "../src/modules/erc721-puppet/IERC721Mintable.sol"; +import { registerERC721 } from "../src/modules/erc721-puppet/registerERC721.sol"; +import { IERC721Errors } from "../src/modules/erc721-puppet/IERC721Errors.sol"; +import { IERC721Events } from "../src/modules/erc721-puppet/IERC721Events.sol"; +import { _erc721SystemId } from "../src/modules/erc721-puppet/utils.sol"; abstract contract ERC721TokenReceiver { function onERC721Received(address, address, uint256, bytes calldata) external virtual returns (bytes4) { @@ -46,84 +64,28 @@ contract WrongReturnDataERC721Recipient is ERC721TokenReceiver { contract NonERC721Recipient {} -contract MockERC721WithHooks is MockERC721 { - uint256 public beforeCounter; - uint256 public afterCounter; - - function _beforeTokenTransfer(address, address, uint256) internal virtual override { - beforeCounter++; - } - - function _afterTokenTransfer(address, address, uint256) internal virtual override { - afterCounter++; - } -} +contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { + using WorldResourceIdInstance for ResourceId; -contract ERC721HooksTest is SoladyTest, ERC721TokenReceiver { - uint256 public expectedBeforeCounter; - uint256 public expectedAfterCounter; - uint256 public ticker; + IBaseWorld world; + ERC721Module erc721Module; + IERC721Mintable token; + address tokenOwner = address(0x123456); - function _checkCounters() internal view { - require(expectedBeforeCounter == MockERC721WithHooks(msg.sender).beforeCounter(), "Before counter mismatch."); - require(expectedAfterCounter == MockERC721WithHooks(msg.sender).afterCounter(), "After counter mismatch."); - } + function setUp() public { + world = IBaseWorld(address(new World())); + world.initialize(new CoreModule()); + world.installModule(new PuppetModule(), new bytes(0)); + StoreSwitch.setStoreAddress(address(world)); - function onERC721Received(address, address, uint256, bytes calldata) external virtual override returns (bytes4) { - _checkCounters(); - return ERC721TokenReceiver.onERC721Received.selector; + // Register a new ERC721 token + vm.prank(tokenOwner); + token = registerERC721(world, "myERC721", ERC721MetadataData({ name: "Token", symbol: "TKN", baseURI: "" })); } - function _testHooks(MockERC721WithHooks token) internal { - address from = _randomNonZeroAddress(); - uint256 tokenId = uint256(keccak256(abi.encode(expectedBeforeCounter, expectedAfterCounter))); - expectedBeforeCounter++; - expectedAfterCounter++; - token.mint(address(this), tokenId); - - expectedBeforeCounter++; - expectedAfterCounter++; - token.transferFrom(address(this), from, tokenId); - - expectedBeforeCounter++; - expectedAfterCounter++; - uint256 r = ticker < 4 ? ticker : _random() % 4; - vm.prank(from); - if (r == 0) { - token.safeTransferFrom(from, address(this), tokenId); - } else if (r == 1) { - token.safeTransferFrom(from, address(this), tokenId, ""); - } else if (r == 2) { - token.directSafeTransferFrom(from, address(this), tokenId); - } else if (r == 3) { - token.directSafeTransferFrom(from, address(this), tokenId, ""); - } else { - revert(); - } - } - - function testERC721Hooks() public { - MockERC721WithHooks token = new MockERC721WithHooks(); - - for (uint256 i; i < 32; ++i) { - _testHooks(token); - } - } -} - -contract ERC721Test is SoladyTest { - MockERC721 token; - - uint256 private constant _ERC721_MASTER_SLOT_SEED = 0x7d8825530a5a2e7a << 192; - - event Transfer(address indexed from, address indexed to, uint256 indexed id); - - event Approval(address indexed owner, address indexed approved, uint256 indexed id); - - event ApprovalForAll(address indexed owner, address indexed operator, bool approved); - - function setUp() public { - token = new MockERC721(); + function _expectAccessDenied(address caller) internal { + ResourceId tokenSystemId = _erc721SystemId("myERC721"); + vm.expectRevert(abi.encodePacked(IWorldErrors.World_AccessDenied.selector, tokenSystemId.toString(), caller)); } function _expectMintEvent(address to, uint256 id) internal { @@ -149,175 +111,10 @@ contract ERC721Test is SoladyTest { emit ApprovalForAll(owner, operator, approved); } - function _aux(address owner) internal pure returns (uint224 result) { - /// @solidity memory-safe-assembly - assembly { - mstore(0x00, owner) - result := shr(32, shl(32, keccak256(0x0c, 0x14))) - } - } - - function _extraData(uint256 id) internal pure returns (uint96 result) { - /// @solidity memory-safe-assembly - assembly { - mstore(0x00, id) - result := shr(160, shl(160, keccak256(0x00, 0x20))) - } - } - - function _transferFrom(address from, address to, uint256 id) internal { - if (_random() % 2 == 0) { - token.transferFrom(from, to, id); - } else { - token.directTransferFrom(from, to, id); - } - } - - function _safeTransferFrom(address from, address to, uint256 id) internal { - if (_random() % 2 == 0) { - token.safeTransferFrom(from, to, id); - } else { - token.directSafeTransferFrom(from, to, id); - } - } - - function _safeTransferFrom(address from, address to, uint256 id, bytes memory data) internal { - if (_random() % 2 == 0) { - token.safeTransferFrom(from, to, id, data); - } else { - token.directSafeTransferFrom(from, to, id, data); - } - } - - function _approve(address spender, uint256 id) internal { - if (_random() % 2 == 0) { - token.approve(spender, id); - } else { - token.directApprove(spender, id); - } - } - - function _setApprovalForAll(address operator, bool approved) internal { - if (_random() % 2 == 0) { - token.setApprovalForAll(operator, approved); - } else { - token.directSetApprovalForAll(operator, approved); - } - } - - function _ownerOf(uint256 id) internal returns (address) { - if (_random() % 2 == 0) { - return token.ownerOf(id); - } else { - return token.directOwnerOf(id); - } - } - - function _getApproved(uint256 id) internal returns (address) { - if (_random() % 2 == 0) { - return token.getApproved(id); - } else { - return token.directGetApproved(id); - } - } - - function _owners() internal returns (address a, address b) { - a = _randomNonZeroAddress(); - b = _randomNonZeroAddress(); - while (a == b) b = _randomNonZeroAddress(); - } - - function testSafetyOfCustomStorage(uint256 id0, uint256 id1) public { - bool safe; - while (id0 == id1) id1 = _random(); - /// @solidity memory-safe-assembly - assembly { - mstore(0x00, id0) - mstore(0x1c, _ERC721_MASTER_SLOT_SEED) - let slot0 := add(id0, add(id0, keccak256(0x00, 0x20))) - let slot2 := add(1, slot0) - mstore(0x00, id1) - mstore(0x1c, _ERC721_MASTER_SLOT_SEED) - let slot1 := add(id1, add(id1, keccak256(0x00, 0x20))) - let slot3 := add(1, slot1) - safe := 1 - if eq(slot0, slot1) { - safe := 0 - } - if eq(slot0, slot2) { - safe := 0 - } - if eq(slot0, slot3) { - safe := 0 - } - if eq(slot1, slot2) { - safe := 0 - } - if eq(slot1, slot3) { - safe := 0 - } - if eq(slot2, slot3) { - safe := 0 - } - } - require(safe, "Custom storage not safe"); - } - - function testAuthorizedEquivalence(address by, bool isOwnerOrOperator, bool isApprovedAccount) public { - bool a = true; - bool b = true; - /// @solidity memory-safe-assembly - assembly { - if by { - if iszero(isOwnerOrOperator) { - a := isApprovedAccount - } - } - if iszero(or(iszero(by), isOwnerOrOperator)) { - b := isApprovedAccount - } - } - assertEq(a, b); - } - - function testCannotExceedMaxBalance() public { - bytes32 balanceSlot; - (address owner0, address owner1) = _owners(); - - /// @solidity memory-safe-assembly - assembly { - mstore(0x1c, _ERC721_MASTER_SLOT_SEED) - mstore(0x00, owner0) - balanceSlot := keccak256(0x0c, 0x1c) - } - - vm.store(address(token), balanceSlot, bytes32(uint256(0xfffffffe))); - token.setAux(owner0, type(uint224).max); - assertEq(token.balanceOf(owner0), 0xfffffffe); - assertEq(token.getAux(owner0), type(uint224).max); - token.mint(owner0, 0); - assertEq(token.balanceOf(owner0), 0xffffffff); - - vm.expectRevert(ERC721.AccountBalanceOverflow.selector); - token.mint(owner0, 1); - - token.uncheckedBurn(0); - assertEq(token.balanceOf(owner0), 0xfffffffe); - - token.mint(owner1, 0); - vm.prank(owner1); - _transferFrom(owner1, owner0, 0); - - token.mint(owner1, 1); - vm.expectRevert(ERC721.AccountBalanceOverflow.selector); - vm.prank(owner1); - _transferFrom(owner1, owner0, 1); - assertEq(token.getAux(owner0), type(uint224).max); - } - - function testMint(uint256 id) public { - address owner = _randomNonZeroAddress(); + function testMint(uint256 id, address owner) public { + vm.assume(owner != address(0)); + vm.prank(tokenOwner); _expectMintEvent(owner, id); token.mint(owner, id); @@ -325,459 +122,200 @@ contract ERC721Test is SoladyTest { assertEq(_ownerOf(id), owner); } - function testBurn(uint256 id) public { - address owner = _randomNonZeroAddress(); + function testMintRevertAccessDenied(uint256 id, address owner) public { + vm.assume(owner != address(0)); + + _expectAccessDenied(address(this)); + token.mint(owner, id); + } + + function testBurn(uint256 id, address owner) public { + vm.assume(owner != address(0)); _expectMintEvent(owner, id); token.mint(owner, id); - if (_random() % 2 == 0) { - _expectBurnEvent(owner, id); - token.uncheckedBurn(id); - } else { - vm.expectRevert(ERC721.NotOwnerNorApproved.selector); - token.burn(id); - uint256 r = _random() % 3; - if (r == 0) { - vm.prank(owner); - _transferFrom(owner, address(this), id); - _expectBurnEvent(address(this), id); - token.burn(id); - } - if (r == 1) { - vm.prank(owner); - _setApprovalForAll(address(this), true); - _expectBurnEvent(owner, id); - token.burn(id); - } - if (r == 2) { - vm.prank(owner); - _approve(address(this), id); - _expectBurnEvent(owner, id); - token.burn(id); - } - } + vm.prank(tokenOwner); + _expectBurnEvent(owner, id); + token.burn(id); assertEq(token.balanceOf(owner), 0); - vm.expectRevert(ERC721.TokenDoesNotExist.selector); - _ownerOf(id); + vm.expectRevert(abi.encodePacked(ERC721NonexistentToken.selector, id)); + token.ownerOf(id); } - function testTransferFrom() public { - address owner = _randomNonZeroAddress(); - token.mint(owner, 0); + function testBurnRevertAccessDenined() public { + vm.assume(owner != address(0)); + + vm.prank(tokenOwner); + _expectMintEvent(owner, id); + token.mint(owner, id); + + _expectAccessDenied(address(this)); + token.burn(id); + } + + function testTransferFrom(address owner, uint256 tokenId) public { + vm.assume(owner != address(0)); + + vm.prank(tokenOwner); + token.mint(owner, tokenId); + vm.prank(owner); - token.transferFrom(owner, address(this), 0); - } - - function testEverything(uint256) public { - address[2] memory owners; - uint256[][2] memory tokens; - - unchecked { - (owners[0], owners[1]) = _owners(); - for (uint256 j; j != 2; ++j) { - tokens[j] = new uint256[](_random() % 3); - } - - for (uint256 j; j != 2; ++j) { - token.setAux(owners[j], _aux(owners[j])); - for (uint256 i; i != tokens[j].length; ) { - uint256 id = _random(); - if (!token.exists(id)) { - tokens[j][i++] = id; - _expectMintEvent(owners[j], id); - token.mint(owners[j], id); - token.setExtraData(id, _extraData(id)); - } - } - } - for (uint256 j; j != 2; ++j) { - assertEq(token.balanceOf(owners[j]), tokens[j].length); - for (uint256 i; i != tokens[j].length; ++i) { - vm.prank(owners[j]); - _expectApprovalEvent(owners[j], address(this), tokens[j][i]); - _approve(address(this), tokens[j][i]); - } - } - for (uint256 j; j != 2; ++j) { - for (uint256 i; i != tokens[j].length; ++i) { - assertEq(_getApproved(tokens[j][i]), address(this)); - uint256 fromBalanceBefore = token.balanceOf(owners[j]); - uint256 toBalanceBefore = token.balanceOf(owners[j ^ 1]); - _expectTransferEvent(owners[j], owners[j ^ 1], tokens[j][i]); - _transferFrom(owners[j], owners[j ^ 1], tokens[j][i]); - assertEq(token.balanceOf(owners[j]), fromBalanceBefore - 1); - assertEq(token.balanceOf(owners[j ^ 1]), toBalanceBefore + 1); - assertEq(_getApproved(tokens[j][i]), address(0)); - } - } - for (uint256 j; j != 2; ++j) { - for (uint256 i; i != tokens[j].length; ++i) { - assertEq(_ownerOf(tokens[j][i]), owners[j ^ 1]); - assertEq(token.getExtraData(tokens[j][i]), _extraData(tokens[j][i])); - } - } - if (_random() % 2 == 0) { - for (uint256 j; j != 2; ++j) { - for (uint256 i; i != tokens[j].length; ++i) { - vm.expectRevert(ERC721.NotOwnerNorApproved.selector); - _transferFrom(owners[j ^ 1], owners[j], tokens[j][i]); - vm.prank(owners[j ^ 1]); - _expectApprovalEvent(owners[j ^ 1], address(this), tokens[j][i]); - _approve(address(this), tokens[j][i]); - _expectTransferEvent(owners[j ^ 1], owners[j], tokens[j][i]); - _transferFrom(owners[j ^ 1], owners[j], tokens[j][i]); - } - } - } else { - for (uint256 j; j != 2; ++j) { - vm.prank(owners[j ^ 1]); - _expectApprovalForAllEvent(owners[j ^ 1], address(this), true); - token.setApprovalForAll(address(this), true); - for (uint256 i; i != tokens[j].length; ++i) { - _expectTransferEvent(owners[j ^ 1], owners[j], tokens[j][i]); - _transferFrom(owners[j ^ 1], owners[j], tokens[j][i]); - } - } - } - for (uint256 j; j != 2; ++j) { - assertEq(token.getAux(owners[j]), _aux(owners[j])); - for (uint256 i; i != tokens[j].length; ++i) { - assertEq(_ownerOf(tokens[j][i]), owners[j]); - assertEq(token.getExtraData(tokens[j][i]), _extraData(tokens[j][i])); - } - } - for (uint256 j; j != 2; ++j) { - for (uint256 i; i != tokens[j].length; ++i) { - token.uncheckedBurn(tokens[j][i]); - } - } - for (uint256 j; j != 2; ++j) { - assertEq(token.balanceOf(owners[j]), 0); - for (uint256 i; i != tokens[j].length; ++i) { - assertEq(token.getExtraData(tokens[j][i]), _extraData(tokens[j][i])); - } - } - } - } - - function testIsApprovedOrOwner(uint256 id) public { - (address owner0, address owner1) = _owners(); - - vm.expectRevert(ERC721.TokenDoesNotExist.selector); - token.isApprovedOrOwner(owner0, id); - - token.mint(owner0, id); - assertEq(token.isApprovedOrOwner(owner0, id), true); - - vm.prank(owner0); - _transferFrom(owner0, owner1, id); - assertEq(token.isApprovedOrOwner(owner0, id), false); - - vm.prank(owner1); - _setApprovalForAll(owner0, true); - assertEq(token.isApprovedOrOwner(owner0, id), true); - - vm.prank(owner1); - _setApprovalForAll(owner0, false); - assertEq(token.isApprovedOrOwner(owner0, id), false); - - vm.prank(owner1); - _approve(owner0, id); - assertEq(token.isApprovedOrOwner(owner0, id), true); - } - - function testExtraData(uint256 id) public { - (address owner0, address owner1) = _owners(); - - bool setExtraData = _random() % 2 == 0; - uint96 extraData = uint96(_bound(_random(), 0, type(uint96).max)); - if (setExtraData) { - token.setExtraData(id, extraData); - } - _expectMintEvent(owner0, id); - token.mint(owner0, id); - if (setExtraData) { - assertEq(token.getExtraData(id), extraData); - } else { - assertEq(token.getExtraData(id), 0); - } - - vm.prank(owner0); - _expectTransferEvent(owner0, owner1, id); - _transferFrom(owner0, owner1, id); - if (setExtraData) { - assertEq(token.getExtraData(id), extraData); - } else { - assertEq(token.getExtraData(id), 0); - } - assertEq(_ownerOf(id), owner1); - - if (_random() % 2 == 0) { - extraData = uint96(_bound(_random(), 0, type(uint96).max)); - token.setExtraData(id, extraData); - setExtraData = true; - } - - _expectBurnEvent(owner1, id); - token.uncheckedBurn(id); - if (setExtraData) { - assertEq(token.getExtraData(id), extraData); - } else { - assertEq(token.getExtraData(id), 0); - } - vm.expectRevert(ERC721.TokenDoesNotExist.selector); - _ownerOf(id); - } - - function testExtraData2(uint256 id0, uint256 id1) public { - while (id0 == id1) id1 = _random(); - token.setExtraData(id0, _extraData(id0)); - token.setExtraData(id1, _extraData(id1)); - assertEq(token.getExtraData(id0), _extraData(id0)); - assertEq(token.getExtraData(id1), _extraData(id1)); - } - - function testAux(uint256) public { - (address owner0, address owner1) = _owners(); - - bool setAux = _random() % 2 == 0; - if (setAux) { - token.setAux(owner0, _aux(owner0)); - token.setAux(owner1, _aux(owner1)); - } - - for (uint256 i; i < 2; ++i) { - _expectMintEvent(owner0, i * 2 + 0); - token.mint(owner0, i * 2 + 0); - assertEq(token.balanceOf(owner0), i + 1); - - _expectMintEvent(owner1, i * 2 + 1); - token.mint(owner1, i * 2 + 1); - assertEq(token.balanceOf(owner1), i + 1); - - if (setAux) { - assertEq(token.getAux(owner0), _aux(owner0)); - assertEq(token.getAux(owner1), _aux(owner1)); - } else { - assertEq(token.getAux(owner0), 0); - assertEq(token.getAux(owner1), 0); - } - } - - for (uint256 i; i < 2; ++i) { - _expectBurnEvent(owner0, i * 2 + 0); - token.uncheckedBurn(i * 2 + 0); - assertEq(token.balanceOf(owner0), 1 - i); - - _expectBurnEvent(owner1, i * 2 + 1); - token.uncheckedBurn(i * 2 + 1); - assertEq(token.balanceOf(owner1), 1 - i); - - if (setAux) { - assertEq(token.getAux(owner0), _aux(owner0)); - assertEq(token.getAux(owner1), _aux(owner1)); - } else { - assertEq(token.getAux(owner0), 0); - assertEq(token.getAux(owner1), 0); - } - } - } - - function testApprove(uint256 id) public { - (address spender, ) = _randomSigner(); + token.transferFrom(owner, address(this), tokenId); + assertEq(token.balanceOf(owner), tokenId); + assertEq(token.balanceOf(address(this)), tokenId); + assertEq(token.ownerOf(tokenId), address(this)); + } + + function testApprove(uint256 id, address spender) public { + vm.prank(tokenOwner); token.mint(address(this), id); _expectApprovalEvent(address(this), spender, id); - _approve(spender, id); - assertEq(_getApproved(id), spender); + token.approve(spender, id); + assertEq(token.getApproved(id), spender); } - function testApproveBurn(uint256 id) public { - (address spender, ) = _randomSigner(); - + function testApproveBurn(uint256 id, address spender) public { + vm.prank(tokenOwner); token.mint(address(this), id); - _approve(spender, id); - - token.uncheckedBurn(id); + token.approve(spender, id); + // Burn by sending to 0 address + token.transferFrom(address(this), address(0), id); assertEq(token.balanceOf(address(this)), 0); - vm.expectRevert(ERC721.TokenDoesNotExist.selector); - _getApproved(id); + vm.expectRevert(abi.encodePacked(ERC721NonexistentToken.selector, id)); + token.getApproved(id); - vm.expectRevert(ERC721.TokenDoesNotExist.selector); - _ownerOf(id); + vm.expectRevert(abi.encodePacked(ERC721NonexistentToken.selector, id)); + token.ownerOf(id); } - function testApproveAll(uint256) public { - (address operator, ) = _randomSigner(); - bool approved = _random() % 2 == 0; + function testApproveAll(address operator, bool approved) public { _expectApprovalForAllEvent(address(this), operator, approved); - _setApprovalForAll(operator, approved); + token.setApprovalForAll(operator, approved); assertEq(token.isApprovedForAll(address(this), operator), approved); } - function testTransferFrom(uint256 id) public { - (address from, address to) = _owners(); - + function testTransferFromSelf(uint256 id, address from, address to) public { + vm.prank(tokenOwner); token.mint(from, id); - if (_random() % 2 == 0) { - uint256 r = _random() % 3; - if (r == 0) { - vm.prank(from); - _approve(address(this), id); - _expectTransferEvent(from, to, id); - _transferFrom(from, to, id); - } - if (r == 1) { - vm.prank(from); - _setApprovalForAll(address(this), true); - _expectTransferEvent(from, to, id); - _transferFrom(from, to, id); - } - if (r == 2) { - vm.prank(from); - _expectTransferEvent(from, address(this), id); - _transferFrom(from, address(this), id); - _expectTransferEvent(address(this), to, id); - _transferFrom(address(this), to, id); - } - } else { - (address temp, ) = _randomSigner(); - while (temp == from || temp == to) (temp, ) = _randomSigner(); - if (_random() % 2 == 0) { - _expectTransferEvent(from, temp, id); - token.uncheckedTransferFrom(from, temp, id); - } else { - vm.prank(from); - _expectTransferEvent(from, temp, id); - _transferFrom(from, temp, id); - } - _expectTransferEvent(temp, to, id); - token.uncheckedTransferFrom(temp, to, id); - } - - assertEq(_getApproved(id), address(0)); - assertEq(_ownerOf(id), to); - assertEq(token.balanceOf(to), 1); - assertEq(token.balanceOf(from), 0); - } - - function testTransferFromSelf(uint256 id) public { - (address to, ) = _randomSigner(); - - token.mint(address(this), id); - - _transferFrom(address(this), to, id); + vm.prank(from); + token.transferFrom(from, to, id); - assertEq(_getApproved(id), address(0)); - assertEq(_ownerOf(id), to); + assertEq(token.getApproved(id), address(0)); + assertEq(token.ownerOf(id), to); assertEq(token.balanceOf(to), 1); - assertEq(token.balanceOf(address(this)), 0); + assertEq(token.balanceOf(from), 0); } - function testTransferFromApproveAll(uint256 id) public { - (address from, address to) = _owners(); - + function testTransferFromApproveAll(uint256 id, address from, address to, address operator) public { + vm.prank(tokenOwner); token.mint(from, id); vm.prank(from); - _setApprovalForAll(address(this), true); + token.setApprovalForAll(operator, true); - _transferFrom(from, to, id); + vm.prank(operator); + token.transferFrom(from, to, id); - assertEq(_getApproved(id), address(0)); - assertEq(_ownerOf(id), to); + assertEq(token.getApproved(id), address(0)); + assertEq(token.ownerOf(id), to); assertEq(token.balanceOf(to), 1); assertEq(token.balanceOf(from), 0); } - function testSafeTransferFromToEOA(uint256 id) public { - (address from, address to) = _owners(); - + function testSafeTransferFromToEOA(uint256 id, address from, address to, address operator) public { + vm.prank(tokenOwner); token.mint(from, id); vm.prank(from); - _setApprovalForAll(address(this), true); + token.setApprovalForAll(operator, true); - _safeTransferFrom(from, to, id); + vm.prank(operator); + token.safeTransferFrom(from, to, id); - assertEq(_getApproved(id), address(0)); - assertEq(_ownerOf(id), to); + assertEq(token.getApproved(id), address(0)); + assertEq(token.ownerOf(id), to); assertEq(token.balanceOf(to), 1); assertEq(token.balanceOf(from), 0); } - function testSafeTransferFromToERC721Recipient(uint256 id) public { - (address from, ) = _randomSigner(); - + function testSafeTransferFromToERC721Recipient(uint256 id, address from, address operator) public { ERC721Recipient recipient = new ERC721Recipient(); + vm.prank(tokenOwner); token.mint(from, id); vm.prank(from); - _setApprovalForAll(address(this), true); + token.setApprovalForAll(operator, true); - _safeTransferFrom(from, address(recipient), id); + vm.prank(operator); + token.safeTransferFrom(from, address(recipient), id); - assertEq(_getApproved(id), address(0)); - assertEq(_ownerOf(id), address(recipient)); + assertEq(token.getApproved(id), address(0)); + assertEq(token.ownerOf(id), address(recipient)); assertEq(token.balanceOf(address(recipient)), 1); assertEq(token.balanceOf(from), 0); - assertEq(recipient.operator(), address(this)); + assertEq(recipient.operator(), operator); assertEq(recipient.from(), from); assertEq(recipient.id(), id); assertEq(recipient.data(), ""); } - function testSafeTransferFromToERC721RecipientWithData(uint256 id, bytes memory data) public { - (address from, ) = _randomSigner(); - + function testSafeTransferFromToERC721RecipientWithData( + uint256 id, + address from, + address operator, + bytes memory data + ) public { ERC721Recipient recipient = new ERC721Recipient(); + vm.prank(tokenOwner); token.mint(from, id); vm.prank(from); - _setApprovalForAll(address(this), true); + token.setApprovalForAll(operator, true); - _safeTransferFrom(from, address(recipient), id, data); + vm.prank(operator); + token.safeTransferFrom(from, address(recipient), id, data); assertEq(recipient.data(), data); assertEq(recipient.id(), id); - assertEq(recipient.operator(), address(this)); + assertEq(recipient.operator(), operator); assertEq(recipient.from(), from); - assertEq(_getApproved(id), address(0)); - assertEq(_ownerOf(id), address(recipient)); + assertEq(token.getApproved(id), address(0)); + assertEq(token.ownerOf(id), address(recipient)); assertEq(token.balanceOf(address(recipient)), 1); assertEq(token.balanceOf(from), 0); } - function testSafeMintToEOA(uint256 id) public { - (address to, ) = _randomSigner(); - + function testSafeMintToEOA(uint256 id, address to) public { + vm.prank(tokenOwner); token.safeMint(to, id); - assertEq(_ownerOf(id), address(to)); + assertEq(token.ownerOf(id), address(to)); assertEq(token.balanceOf(address(to)), 1); } function testSafeMintToERC721Recipient(uint256 id) public { ERC721Recipient to = new ERC721Recipient(); + vm.prank(tokenOwner); token.safeMint(address(to), id); - assertEq(_ownerOf(id), address(to)); + assertEq(token.ownerOf(id), address(to)); assertEq(token.balanceOf(address(to)), 1); - assertEq(to.operator(), address(this)); + assertEq(to.operator(), tokenOwner); assertEq(to.from(), address(0)); assertEq(to.id(), id); assertEq(to.data(), ""); @@ -786,170 +324,219 @@ contract ERC721Test is SoladyTest { function testSafeMintToERC721RecipientWithData(uint256 id, bytes memory data) public { ERC721Recipient to = new ERC721Recipient(); + vm.prank(tokenOwner); token.safeMint(address(to), id, data); - assertEq(_ownerOf(id), address(to)); + assertEq(token.ownerOf(id), address(to)); assertEq(token.balanceOf(address(to)), 1); - assertEq(to.operator(), address(this)); + assertEq(to.operator(), tokenOwner); assertEq(to.from(), address(0)); assertEq(to.id(), id); assertEq(to.data(), data); } function testMintToZeroReverts(uint256 id) public { - vm.expectRevert(ERC721.TransferToZeroAddress.selector); + vm.expectRevert(abi.encodePacked(ERC721InvalidReceiver.selector, address(0))); + vm.prank(tokenOwner); token.mint(address(0), id); } - function testDoubleMintReverts(uint256 id) public { - (address to, ) = _randomSigner(); - + function testDoubleMintReverts(uint256 id, address to) public { + vm.prank(tokenOwner); token.mint(to, id); - vm.expectRevert(ERC721.TokenAlreadyExists.selector); + + vm.prank(tokenOwner); + vm.expectRevert(abi.encodePacked(ERC721InvalidSender.selector, address(0))); token.mint(to, id); } function testBurnNonExistentReverts(uint256 id) public { - vm.expectRevert(ERC721.TokenDoesNotExist.selector); - token.uncheckedBurn(id); + vm.prank(tokenOwner); + vm.expectRevert(abi.encodePacked(ERC721NonexistentToken.selector, id)); + token.burn(id); } - function testDoubleBurnReverts(uint256 id) public { - (address to, ) = _randomSigner(); - + function testDoubleBurnReverts(uint256 id, address to) public { + vm.prank(tokenOwner); token.mint(to, id); - token.uncheckedBurn(id); - vm.expectRevert(ERC721.TokenDoesNotExist.selector); - token.uncheckedBurn(id); + vm.prank(tokenOwner); + token.burn(id); + + vm.prank(tokenOwner); + vm.expectRevert(abi.encodePacked(ERC721NonexistentToken.selector, id)); + token.burn(id); } function testApproveNonExistentReverts(uint256 id, address to) public { - vm.expectRevert(ERC721.TokenDoesNotExist.selector); - _approve(to, id); + vm.expectRevert(abi.encodePacked(ERC721NonexistentToken.selector, id)); + token.approve(to, id); } - function testApproveUnauthorizedReverts(uint256 id) public { - (address owner, address to) = _owners(); - + function testApproveUnauthorizedReverts(uint256 id, address owner, address to) public { + vm.prank(tokenOwner); token.mint(owner, id); - vm.expectRevert(ERC721.NotOwnerNorApproved.selector); - _approve(to, id); + + vm.expectRevert(abi.encodePacked(ERC721InvalidApprover.selector, address(this))); + token.approve(to, id); } function testTransferFromNotExistentReverts(address from, address to, uint256 id) public { - vm.expectRevert(ERC721.TokenDoesNotExist.selector); - _transferFrom(from, to, id); + vm.expectRevert(abi.encodePacked(ERC721NonexistentToken.selector, id)); + token.transferFrom(from, to, id); } - function testTransferFromWrongFromReverts(address to, uint256 id) public { - (address owner, address from) = _owners(); - + function testTransferFromWrongFromReverts(address to, uint256 id, address owner, address from) public { + vm.prank(tokenOwner); token.mint(owner, id); - vm.expectRevert(ERC721.TransferFromIncorrectOwner.selector); - _transferFrom(from, to, id); + + vm.expectRevert(abi.encodePacked(ERC721IncorrectOwner.selector, from, id, owner)); + token.transferFrom(from, to, id); } function testTransferFromToZeroReverts(uint256 id) public { + vm.prank(tokenOwner); token.mint(address(this), id); - vm.expectRevert(ERC721.TransferToZeroAddress.selector); - _transferFrom(address(this), address(0), id); + vm.expectRevert(abi.encodePacked(ERC721InvalidReceiver.selector, address(0))); + token.transferFrom(address(this), address(0), id); } - function testTransferFromNotOwner(uint256 id) public { - (address from, address to) = _owners(); + function testTransferFromNotOwner(uint256 id, address from, address to, address operator) public { + vm.assume(operator != from); + vm.prank(tokenOwner); token.mint(from, id); - vm.expectRevert(ERC721.NotOwnerNorApproved.selector); - _transferFrom(from, to, id); + vm.prank(operator); + vm.expectRevert(abi.encodePacked(ERC721InsufficientApproval.selector, operator, id)); + token.transferFrom(from, to, id); } - function testSafeTransferFromToNonERC721RecipientReverts(uint256 id) public { - token.mint(address(this), id); + function testSafeTransferFromToNonERC721RecipientReverts(uint256 id, address from) public { + vm.prank(tokenOwner); + token.mint(from, id); + address to = address(new NonERC721Recipient()); - vm.expectRevert(ERC721.TransferToNonERC721ReceiverImplementer.selector); - _safeTransferFrom(address(this), address(to), id); + + vm.prank(from); + vm.expectRevert(abi.encodePacked(ERC721InvalidReceiver.selector, to)); + token.safeTransferFrom(from, to, id); } - function testSafeTransferFromToNonERC721RecipientWithDataReverts(uint256 id, bytes memory data) public { - token.mint(address(this), id); + function testSafeTransferFromToNonERC721RecipientWithDataReverts(uint256 id, address from, bytes memory data) public { + vm.prank(tokenOwner); + token.mint(from, id); + address to = address(new NonERC721Recipient()); - vm.expectRevert(ERC721.TransferToNonERC721ReceiverImplementer.selector); - _safeTransferFrom(address(this), to, id, data); + + vm.prank(from); + vm.expectRevert(abi.encodePacked(ERC721InvalidReceiver.selector, to)); + token.safeTransferFrom(from, to, id, data); } - function testSafeTransferFromToRevertingERC721RecipientReverts(uint256 id) public { - token.mint(address(this), id); + function testSafeTransferFromToRevertingERC721RecipientReverts(uint256 id, address from) public { + vm.prank(tokenOwner); + token.mint(from, id); + address to = address(new RevertingERC721Recipient()); - vm.expectRevert(abi.encodePacked(ERC721TokenReceiver.onERC721Received.selector)); - _safeTransferFrom(address(this), to, id); + + vm.prank(from); + vm.expectRevert(abi.encodePacked(ERC721InvalidReceiver.selector, to)); + token.safeTransferFrom(from, to, id); } - function testSafeTransferFromToRevertingERC721RecipientWithDataReverts(uint256 id, bytes memory data) public { - token.mint(address(this), id); + function testSafeTransferFromToRevertingERC721RecipientWithDataReverts( + uint256 id, + address from, + bytes memory data + ) public { + vm.prank(tokenOwner); + token.mint(from, id); + address to = address(new RevertingERC721Recipient()); + + vm.prank(from); vm.expectRevert(abi.encodePacked(ERC721TokenReceiver.onERC721Received.selector)); - _safeTransferFrom(address(this), to, id, data); + token.safeTransferFrom(from, to, id, data); } - function testSafeTransferFromToERC721RecipientWithWrongReturnDataReverts(uint256 id) public { - token.mint(address(this), id); + function testSafeTransferFromToERC721RecipientWithWrongReturnDataReverts(uint256 id, address from) public { + vm.prank(tokenOwner); + token.mint(from, id); + address to = address(new WrongReturnDataERC721Recipient()); - vm.expectRevert(ERC721.TransferToNonERC721ReceiverImplementer.selector); - _safeTransferFrom(address(this), to, id); + + vm.prank(from); + vm.expectRevert(abi.encodePacked(ERC721InvalidReceiver.selector, to)); + token.safeTransferFrom(from, to, id); } function testSafeTransferFromToERC721RecipientWithWrongReturnDataWithDataReverts( uint256 id, + address from, bytes memory data ) public { - token.mint(address(this), id); + vm.prank(tokenOwner); + token.mint(from, id); + address to = address(new WrongReturnDataERC721Recipient()); - vm.expectRevert(ERC721.TransferToNonERC721ReceiverImplementer.selector); - _safeTransferFrom(address(this), to, id, data); + + vm.prank(from); + vm.expectRevert(abi.encodePacked(ERC721InvalidReceiver.selector, to)); + token.safeTransferFrom(from, to, id, data); } function testSafeMintToNonERC721RecipientReverts(uint256 id) public { address to = address(new NonERC721Recipient()); - vm.expectRevert(ERC721.TransferToNonERC721ReceiverImplementer.selector); + + vm.prank(tokenOwner); + vm.expectRevert(abi.encodePacked(ERC721InvalidReceiver.selector, to)); token.safeMint(to, id); } function testSafeMintToNonERC721RecipientWithDataReverts(uint256 id, bytes memory data) public { address to = address(new NonERC721Recipient()); - vm.expectRevert(ERC721.TransferToNonERC721ReceiverImplementer.selector); + + vm.prank(tokenOwner); + vm.expectRevert(abi.encodePacked(ERC721InvalidReceiver.selector, to)); token.safeMint(to, id, data); } function testSafeMintToRevertingERC721RecipientReverts(uint256 id) public { address to = address(new RevertingERC721Recipient()); + + vm.prank(tokenOwner); vm.expectRevert(abi.encodePacked(ERC721TokenReceiver.onERC721Received.selector)); token.safeMint(to, id); } function testSafeMintToRevertingERC721RecipientWithDataReverts(uint256 id, bytes memory data) public { address to = address(new RevertingERC721Recipient()); + + vm.prank(tokenOwner); vm.expectRevert(abi.encodePacked(ERC721TokenReceiver.onERC721Received.selector)); token.safeMint(to, id, data); } function testSafeMintToERC721RecipientWithWrongReturnData(uint256 id) public { address to = address(new WrongReturnDataERC721Recipient()); - vm.expectRevert(ERC721.TransferToNonERC721ReceiverImplementer.selector); + + vm.prank(tokenOwner); + vm.expectRevert(abi.encodePacked(ERC721InvalidReceiver.selector, to)); token.safeMint(to, id); } function testSafeMintToERC721RecipientWithWrongReturnDataWithData(uint256 id, bytes memory data) public { address to = address(new WrongReturnDataERC721Recipient()); - vm.expectRevert(ERC721.TransferToNonERC721ReceiverImplementer.selector); + + vm.prank(tokenOwner); + vm.expectRevert(abi.encodePacked(ERC721InvalidReceiver.selector, to)); token.safeMint(to, id, data); } function testOwnerOfNonExistent(uint256 id) public { - vm.expectRevert(ERC721.TokenDoesNotExist.selector); - _ownerOf(id); + token.ownerOf(id); } } From 00bc3627a72df1bce5dc4ef2e5f9aeeb2d7e88b9 Mon Sep 17 00:00:00 2001 From: alvrs Date: Mon, 30 Oct 2023 11:53:45 +0100 Subject: [PATCH 10/27] make it compile --- .../modules/erc20-puppet/registerERC20.sol | 4 +-- .../modules/erc721-puppet/ERC721System.sol | 2 +- .../modules/erc721-puppet/IERC721Mintable.sol | 19 +++++++++++ .../modules/erc721-puppet/registerERC721.sol | 34 +++++++++---------- packages/world-modules/test/ERC721.t.sol | 4 +-- 5 files changed, 41 insertions(+), 22 deletions(-) diff --git a/packages/world-modules/src/modules/erc20-puppet/registerERC20.sol b/packages/world-modules/src/modules/erc20-puppet/registerERC20.sol index 7b14f89568..fb9d780a95 100644 --- a/packages/world-modules/src/modules/erc20-puppet/registerERC20.sol +++ b/packages/world-modules/src/modules/erc20-puppet/registerERC20.sol @@ -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"; /** @@ -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)); diff --git a/packages/world-modules/src/modules/erc721-puppet/ERC721System.sol b/packages/world-modules/src/modules/erc721-puppet/ERC721System.sol index 08653691a1..b90cee51db 100644 --- a/packages/world-modules/src/modules/erc721-puppet/ERC721System.sol +++ b/packages/world-modules/src/modules/erc721-puppet/ERC721System.sol @@ -177,7 +177,7 @@ contract ERC721System is IERC721Mintable, System, PuppetMaster { } /** - * @dev Same as {xref-ERC721-safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is + * @dev Same as {xref-ERC721-safeMint-address-uint256-}[`safeMint`], with an additional `data` parameter which is * forwarded in {IERC721Receiver-onERC721Received} to contract recipients. */ function safeMint(address to, uint256 tokenId, bytes memory data) public virtual { diff --git a/packages/world-modules/src/modules/erc721-puppet/IERC721Mintable.sol b/packages/world-modules/src/modules/erc721-puppet/IERC721Mintable.sol index b09f6fd88a..6a7e570e7f 100644 --- a/packages/world-modules/src/modules/erc721-puppet/IERC721Mintable.sol +++ b/packages/world-modules/src/modules/erc721-puppet/IERC721Mintable.sol @@ -20,6 +20,25 @@ interface IERC721Mintable is IERC721 { */ function mint(address to, uint256 tokenId) external; + /** + * @dev Mints `tokenId`, transfers it to `to` and checks for `to` acceptance. + * + * Requirements: + * + * - caller must own the namespace + * - `tokenId` must not exist. + * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. + * + * Emits a {Transfer} event. + */ + function safeMint(address to, uint256 tokenId) external; + + /** + * @dev Same as {xref-ERC721-safeMint-address-uint256-}[`safeMint`], with an additional `data` parameter which is + * forwarded in {IERC721Receiver-onERC721Received} to contract recipients. + */ + function safeMint(address to, uint256 tokenId, bytes memory data) external; + /** * @dev Destroys `tokenId`. * The approval is cleared when the token is burned. diff --git a/packages/world-modules/src/modules/erc721-puppet/registerERC721.sol b/packages/world-modules/src/modules/erc721-puppet/registerERC721.sol index 40b0d34874..b02415e779 100644 --- a/packages/world-modules/src/modules/erc721-puppet/registerERC721.sol +++ b/packages/world-modules/src/modules/erc721-puppet/registerERC721.sol @@ -7,31 +7,31 @@ import { WorldResourceIdLib } from "@latticexyz/world/src/WorldResourceId.sol"; import { SystemSwitch } from "../../utils/SystemSwitch.sol"; -import { ERC20Module } from "./ERC20Module.sol"; -import { MODULE_NAMESPACE_ID, ERC20_REGISTRY_TABLE_ID } from "./constants.sol"; -import { IERC20Mintable } from "./IERC20Mintable.sol"; +import { ERC721Module } from "./ERC721Module.sol"; +import { MODULE_NAMESPACE_ID, ERC721_REGISTRY_TABLE_ID } from "./constants.sol"; +import { IERC721Mintable } from "./IERC721Mintable.sol"; -import { MetadataData } from "./tables/Metadata.sol"; -import { ERC20Registry } from "./tables/ERC20Registry.sol"; +import { ERC721MetadataData } from "./tables/ERC721Metadata.sol"; +import { ERC721Registry } from "./tables/ERC721Registry.sol"; /** - * @notice Register a new ERC20 token with the given metadata in a given namespace + * @notice Register a new ERC721 token with the given metadata in a given namespace * @dev This function must be called within a Store context (i.e. using StoreSwitch.setStoreAddress()) */ -function registerERC20( +function registerERC721( IBaseWorld world, bytes14 namespace, - MetadataData memory metadata -) returns (IERC20Mintable token) { - // Get the ERC20 module - ERC20Module erc20Module = ERC20Module(NamespaceOwner.get(MODULE_NAMESPACE_ID)); - if (address(erc20Module) == address(0)) { - erc20Module = new ERC20Module(); + ERC721MetadataData memory metadata +) returns (IERC721Mintable token) { + // Get the ERC721 module + ERC721Module erc721Module = ERC721Module(NamespaceOwner.get(MODULE_NAMESPACE_ID)); + if (address(erc721Module) == address(0)) { + erc721Module = new ERC721Module(); } - // Install the ERC20 module with the provided args - world.installModule(erc20Module, abi.encode(namespace, metadata)); + // Install the ERC721 module with the provided args + world.installModule(erc721Module, abi.encode(namespace, metadata)); - // Return the newly created ERC20 token - token = IERC20Mintable(ERC20Registry.get(ERC20_REGISTRY_TABLE_ID, WorldResourceIdLib.encodeNamespace(namespace))); + // Return the newly created ERC721 token + token = IERC721Mintable(ERC721Registry.get(ERC721_REGISTRY_TABLE_ID, WorldResourceIdLib.encodeNamespace(namespace))); } diff --git a/packages/world-modules/test/ERC721.t.sol b/packages/world-modules/test/ERC721.t.sol index b3fbe3fc90..389aaec875 100644 --- a/packages/world-modules/test/ERC721.t.sol +++ b/packages/world-modules/test/ERC721.t.sol @@ -119,7 +119,7 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { token.mint(owner, id); assertEq(token.balanceOf(owner), 1); - assertEq(_ownerOf(id), owner); + assertEq(token.ownerOf(id), owner); } function testMintRevertAccessDenied(uint256 id, address owner) public { @@ -145,7 +145,7 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { token.ownerOf(id); } - function testBurnRevertAccessDenined() public { + function testBurnRevertAccessDenined(uint256 id, address owner) public { vm.assume(owner != address(0)); vm.prank(tokenOwner); From 17649365b84b9e677ea0e2bbd660b83b9432c162 Mon Sep 17 00:00:00 2001 From: alvrs Date: Mon, 30 Oct 2023 12:23:38 +0100 Subject: [PATCH 11/27] vm assumptions --- packages/world-modules/test/ERC721.t.sol | 162 +++++++++++++---------- 1 file changed, 95 insertions(+), 67 deletions(-) diff --git a/packages/world-modules/test/ERC721.t.sol b/packages/world-modules/test/ERC721.t.sol index 389aaec875..2c451f093a 100644 --- a/packages/world-modules/test/ERC721.t.sol +++ b/packages/world-modules/test/ERC721.t.sol @@ -70,7 +70,6 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { IBaseWorld world; ERC721Module erc721Module; IERC721Mintable token; - address tokenOwner = address(0x123456); function setUp() public { world = IBaseWorld(address(new World())); @@ -79,7 +78,6 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { StoreSwitch.setStoreAddress(address(world)); // Register a new ERC721 token - vm.prank(tokenOwner); token = registerERC721(world, "myERC721", ERC721MetadataData({ name: "Token", symbol: "TKN", baseURI: "" })); } @@ -111,10 +109,29 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { emit ApprovalForAll(owner, operator, approved); } + function _assumeDifferentNonZero(address address1, address address2) internal pure { + vm.assume(address1 != address(0)); + vm.assume(address2 != address(0)); + vm.assume(address1 != address2); + } + + function _assumeDifferentNonZero(address address1, address address2, address address3) internal pure { + vm.assume(address1 != address(0)); + vm.assume(address2 != address(0)); + vm.assume(address3 != address(0)); + vm.assume(address1 != address2); + vm.assume(address2 != address3); + vm.assume(address3 != address1); + } + + function testSetUp() public { + assertTrue(address(token) != address(0)); + assertEq(NamespaceOwner.get(WorldResourceIdLib.encodeNamespace("myERC721")), address(this)); + } + function testMint(uint256 id, address owner) public { vm.assume(owner != address(0)); - vm.prank(tokenOwner); _expectMintEvent(owner, id); token.mint(owner, id); @@ -122,10 +139,11 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { assertEq(token.ownerOf(id), owner); } - function testMintRevertAccessDenied(uint256 id, address owner) public { - vm.assume(owner != address(0)); + function testMintRevertAccessDenied(uint256 id, address owner, address operator) public { + _assumeDifferentNonZero(owner, operator); - _expectAccessDenied(address(this)); + _expectAccessDenied(operator); + vm.prank(operator); token.mint(owner, id); } @@ -135,7 +153,6 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { _expectMintEvent(owner, id); token.mint(owner, id); - vm.prank(tokenOwner); _expectBurnEvent(owner, id); token.burn(id); @@ -145,65 +162,76 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { token.ownerOf(id); } - function testBurnRevertAccessDenined(uint256 id, address owner) public { - vm.assume(owner != address(0)); + function testBurnRevertAccessDenined(uint256 id, address owner, address operator) public { + _assumeDifferentNonZero(owner, operator); - vm.prank(tokenOwner); _expectMintEvent(owner, id); token.mint(owner, id); _expectAccessDenied(address(this)); + vm.prank(operator); token.burn(id); } - function testTransferFrom(address owner, uint256 tokenId) public { - vm.assume(owner != address(0)); + function testTransferFrom(address owner, address to, uint256 tokenId) public { + _assumeDifferentNonZero(owner, to); - vm.prank(tokenOwner); token.mint(owner, tokenId); vm.prank(owner); - token.transferFrom(owner, address(this), tokenId); + token.transferFrom(owner, to, tokenId); - assertEq(token.balanceOf(owner), tokenId); - assertEq(token.balanceOf(address(this)), tokenId); - assertEq(token.ownerOf(tokenId), address(this)); + assertEq(token.balanceOf(owner), 0); + assertEq(token.balanceOf(to), 1); + assertEq(token.ownerOf(tokenId), to); } - function testApprove(uint256 id, address spender) public { - vm.prank(tokenOwner); - token.mint(address(this), id); + function testApprove(address owner, uint256 id, address spender) public { + _assumeDifferentNonZero(owner, spender); - _expectApprovalEvent(address(this), spender, id); + token.mint(owner, id); + + _expectApprovalEvent(owner, spender, id); token.approve(spender, id); assertEq(token.getApproved(id), spender); } - function testApproveBurn(uint256 id, address spender) public { - vm.prank(tokenOwner); - token.mint(address(this), id); + function testApproveBurn(address owner, uint256 id, address spender) public { + _assumeDifferentNonZero(owner, spender); + token.mint(owner, id); + + vm.prank(owner); token.approve(spender, id); // Burn by sending to 0 address - token.transferFrom(address(this), address(0), id); - assertEq(token.balanceOf(address(this)), 0); + // TODO: this currently fails, but we should allow burning if approved + vm.prank(owner); + token.transferFrom(owner, address(0), id); + assertEq(token.balanceOf(owner), 0); vm.expectRevert(abi.encodePacked(ERC721NonexistentToken.selector, id)); token.getApproved(id); vm.expectRevert(abi.encodePacked(ERC721NonexistentToken.selector, id)); token.ownerOf(id); + + // Mint again, expect approval to be cleared + token.mint(owner, id); + assertEq(token.getApproved(id), address(0)); } - function testApproveAll(address operator, bool approved) public { - _expectApprovalForAllEvent(address(this), operator, approved); + function testApproveAll(address owner, address operator, bool approved) public { + _assumeDifferentNonZero(owner, operator); + + _expectApprovalForAllEvent(owner, operator, approved); token.setApprovalForAll(operator, approved); - assertEq(token.isApprovedForAll(address(this), operator), approved); + assertEq(token.isApprovedForAll(owner, operator), approved); } function testTransferFromSelf(uint256 id, address from, address to) public { - vm.prank(tokenOwner); + _assumeDifferentNonZero(from, to); + token.mint(from, id); vm.prank(from); @@ -216,7 +244,8 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { } function testTransferFromApproveAll(uint256 id, address from, address to, address operator) public { - vm.prank(tokenOwner); + _assumeDifferentNonZero(from, to); + token.mint(from, id); vm.prank(from); @@ -232,7 +261,8 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { } function testSafeTransferFromToEOA(uint256 id, address from, address to, address operator) public { - vm.prank(tokenOwner); + _assumeDifferentNonZero(from, to); + token.mint(from, id); vm.prank(from); @@ -248,9 +278,10 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { } function testSafeTransferFromToERC721Recipient(uint256 id, address from, address operator) public { + _assumeDifferentNonZero(from, operator); + ERC721Recipient recipient = new ERC721Recipient(); - vm.prank(tokenOwner); token.mint(from, id); vm.prank(from); @@ -276,9 +307,10 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { address operator, bytes memory data ) public { + _assumeDifferentNonZero(from, operator); + ERC721Recipient recipient = new ERC721Recipient(); - vm.prank(tokenOwner); token.mint(from, id); vm.prank(from); @@ -299,23 +331,23 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { } function testSafeMintToEOA(uint256 id, address to) public { - vm.prank(tokenOwner); + vm.assume(to != address(0)); + token.safeMint(to, id); - assertEq(token.ownerOf(id), address(to)); - assertEq(token.balanceOf(address(to)), 1); + assertEq(token.ownerOf(id), to); + assertEq(token.balanceOf(to), 1); } function testSafeMintToERC721Recipient(uint256 id) public { ERC721Recipient to = new ERC721Recipient(); - vm.prank(tokenOwner); token.safeMint(address(to), id); assertEq(token.ownerOf(id), address(to)); assertEq(token.balanceOf(address(to)), 1); - assertEq(to.operator(), tokenOwner); + assertEq(to.operator(), address(this)); assertEq(to.from(), address(0)); assertEq(to.id(), id); assertEq(to.data(), ""); @@ -324,13 +356,12 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { function testSafeMintToERC721RecipientWithData(uint256 id, bytes memory data) public { ERC721Recipient to = new ERC721Recipient(); - vm.prank(tokenOwner); token.safeMint(address(to), id, data); assertEq(token.ownerOf(id), address(to)); assertEq(token.balanceOf(address(to)), 1); - assertEq(to.operator(), tokenOwner); + assertEq(to.operator(), address(this)); assertEq(to.from(), address(0)); assertEq(to.id(), id); assertEq(to.data(), data); @@ -338,33 +369,29 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { function testMintToZeroReverts(uint256 id) public { vm.expectRevert(abi.encodePacked(ERC721InvalidReceiver.selector, address(0))); - vm.prank(tokenOwner); token.mint(address(0), id); } function testDoubleMintReverts(uint256 id, address to) public { - vm.prank(tokenOwner); + vm.assume(to != address(0)); token.mint(to, id); - vm.prank(tokenOwner); vm.expectRevert(abi.encodePacked(ERC721InvalidSender.selector, address(0))); token.mint(to, id); } function testBurnNonExistentReverts(uint256 id) public { - vm.prank(tokenOwner); vm.expectRevert(abi.encodePacked(ERC721NonexistentToken.selector, id)); token.burn(id); } function testDoubleBurnReverts(uint256 id, address to) public { - vm.prank(tokenOwner); + vm.assume(to != address(0)); + token.mint(to, id); - vm.prank(tokenOwner); token.burn(id); - vm.prank(tokenOwner); vm.expectRevert(abi.encodePacked(ERC721NonexistentToken.selector, id)); token.burn(id); } @@ -374,11 +401,13 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { token.approve(to, id); } - function testApproveUnauthorizedReverts(uint256 id, address owner, address to) public { - vm.prank(tokenOwner); + function testApproveUnauthorizedReverts(uint256 id, address owner, address operator, address to) public { + _assumeDifferentNonZero(owner, operator, to); + token.mint(owner, id); - vm.expectRevert(abi.encodePacked(ERC721InvalidApprover.selector, address(this))); + vm.expectRevert(abi.encodePacked(ERC721InvalidApprover.selector, operator)); + vm.prank(operator); token.approve(to, id); } @@ -388,7 +417,7 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { } function testTransferFromWrongFromReverts(address to, uint256 id, address owner, address from) public { - vm.prank(tokenOwner); + _assumeDifferentNonZero(owner, from, to); token.mint(owner, id); vm.expectRevert(abi.encodePacked(ERC721IncorrectOwner.selector, from, id, owner)); @@ -396,7 +425,6 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { } function testTransferFromToZeroReverts(uint256 id) public { - vm.prank(tokenOwner); token.mint(address(this), id); vm.expectRevert(abi.encodePacked(ERC721InvalidReceiver.selector, address(0))); @@ -404,9 +432,8 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { } function testTransferFromNotOwner(uint256 id, address from, address to, address operator) public { - vm.assume(operator != from); + _assumeDifferentNonZero(from, to, operator); - vm.prank(tokenOwner); token.mint(from, id); vm.prank(operator); @@ -415,7 +442,8 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { } function testSafeTransferFromToNonERC721RecipientReverts(uint256 id, address from) public { - vm.prank(tokenOwner); + vm.assume(from != address(0)); + token.mint(from, id); address to = address(new NonERC721Recipient()); @@ -426,7 +454,8 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { } function testSafeTransferFromToNonERC721RecipientWithDataReverts(uint256 id, address from, bytes memory data) public { - vm.prank(tokenOwner); + vm.assume(from != address(0)); + token.mint(from, id); address to = address(new NonERC721Recipient()); @@ -437,7 +466,8 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { } function testSafeTransferFromToRevertingERC721RecipientReverts(uint256 id, address from) public { - vm.prank(tokenOwner); + vm.assume(from != address(0)); + token.mint(from, id); address to = address(new RevertingERC721Recipient()); @@ -452,7 +482,8 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { address from, bytes memory data ) public { - vm.prank(tokenOwner); + vm.assume(from != address(0)); + token.mint(from, id); address to = address(new RevertingERC721Recipient()); @@ -463,7 +494,8 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { } function testSafeTransferFromToERC721RecipientWithWrongReturnDataReverts(uint256 id, address from) public { - vm.prank(tokenOwner); + vm.assume(from != address(0)); + token.mint(from, id); address to = address(new WrongReturnDataERC721Recipient()); @@ -478,7 +510,8 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { address from, bytes memory data ) public { - vm.prank(tokenOwner); + vm.assume(from != address(0)); + token.mint(from, id); address to = address(new WrongReturnDataERC721Recipient()); @@ -491,7 +524,6 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { function testSafeMintToNonERC721RecipientReverts(uint256 id) public { address to = address(new NonERC721Recipient()); - vm.prank(tokenOwner); vm.expectRevert(abi.encodePacked(ERC721InvalidReceiver.selector, to)); token.safeMint(to, id); } @@ -499,7 +531,6 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { function testSafeMintToNonERC721RecipientWithDataReverts(uint256 id, bytes memory data) public { address to = address(new NonERC721Recipient()); - vm.prank(tokenOwner); vm.expectRevert(abi.encodePacked(ERC721InvalidReceiver.selector, to)); token.safeMint(to, id, data); } @@ -507,7 +538,6 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { function testSafeMintToRevertingERC721RecipientReverts(uint256 id) public { address to = address(new RevertingERC721Recipient()); - vm.prank(tokenOwner); vm.expectRevert(abi.encodePacked(ERC721TokenReceiver.onERC721Received.selector)); token.safeMint(to, id); } @@ -515,7 +545,6 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { function testSafeMintToRevertingERC721RecipientWithDataReverts(uint256 id, bytes memory data) public { address to = address(new RevertingERC721Recipient()); - vm.prank(tokenOwner); vm.expectRevert(abi.encodePacked(ERC721TokenReceiver.onERC721Received.selector)); token.safeMint(to, id, data); } @@ -523,7 +552,6 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { function testSafeMintToERC721RecipientWithWrongReturnData(uint256 id) public { address to = address(new WrongReturnDataERC721Recipient()); - vm.prank(tokenOwner); vm.expectRevert(abi.encodePacked(ERC721InvalidReceiver.selector, to)); token.safeMint(to, id); } @@ -531,12 +559,12 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { function testSafeMintToERC721RecipientWithWrongReturnDataWithData(uint256 id, bytes memory data) public { address to = address(new WrongReturnDataERC721Recipient()); - vm.prank(tokenOwner); vm.expectRevert(abi.encodePacked(ERC721InvalidReceiver.selector, to)); token.safeMint(to, id, data); } function testOwnerOfNonExistent(uint256 id) public { + vm.expectRevert(abi.encodePacked(ERC721NonexistentToken.selector, id)); token.ownerOf(id); } } From 7a83c63fb04cb456385101176f0e209c2e605a9d Mon Sep 17 00:00:00 2001 From: alvrs Date: Mon, 30 Oct 2023 12:36:11 +0100 Subject: [PATCH 12/27] emit event on puppet --- .../src/modules/erc721-puppet/ERC721System.sol | 10 +++++++--- .../world-modules/src/modules/erc721-puppet/utils.sol | 4 ++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/world-modules/src/modules/erc721-puppet/ERC721System.sol b/packages/world-modules/src/modules/erc721-puppet/ERC721System.sol index b90cee51db..37b4adf754 100644 --- a/packages/world-modules/src/modules/erc721-puppet/ERC721System.sol +++ b/packages/world-modules/src/modules/erc721-puppet/ERC721System.sol @@ -305,7 +305,8 @@ contract ERC721System is IERC721Mintable, System, PuppetMaster { Owners.set(_ownersTableId(_namespace()), tokenId, to); - emit Transfer(from, to, tokenId); + // Emit Transfer event on puppet + puppet().log(Transfer.selector, _toBytes32(from), _toBytes32(to), _toBytes32(tokenId), new bytes(0)); return from; } @@ -457,7 +458,8 @@ contract ERC721System is IERC721Mintable, System, PuppetMaster { } if (emitEvent) { - emit Approval(owner, to, tokenId); + // Emit Approval event on puppet + puppet().log(Approval.selector, _toBytes32(owner), _toBytes32(to), _toBytes32(tokenId), new bytes(0)); } } @@ -477,7 +479,9 @@ contract ERC721System is IERC721Mintable, System, PuppetMaster { revert ERC721InvalidOperator(operator); } OperatorApproval.set(_operatorApprovalTableId(_namespace()), owner, operator, approved); - emit ApprovalForAll(owner, operator, approved); + + // Emit ApprovalForAll event on puppet + puppet().log(ApprovalForAll.selector, _toBytes32(owner), _toBytes32(operator), abi.encode(approved)); } /** diff --git a/packages/world-modules/src/modules/erc721-puppet/utils.sol b/packages/world-modules/src/modules/erc721-puppet/utils.sol index 72cf53e742..b093caa559 100644 --- a/packages/world-modules/src/modules/erc721-puppet/utils.sol +++ b/packages/world-modules/src/modules/erc721-puppet/utils.sol @@ -37,6 +37,10 @@ function _toBytes32(address addr) pure returns (bytes32) { return bytes32(uint256(uint160(addr))); } +function _toBytes32(uint256 id) pure returns (bytes32) { + return bytes32(id); +} + function _erc721SystemId(bytes14 namespace) pure returns (ResourceId) { return WorldResourceIdLib.encode({ typeId: RESOURCE_SYSTEM, namespace: namespace, name: ERC721_SYSTEM_NAME }); } From 82c2ecc3731fa181d76012afd9e9507284fb217d Mon Sep 17 00:00:00 2001 From: alvrs Date: Mon, 30 Oct 2023 12:46:17 +0100 Subject: [PATCH 13/27] fix expect revert encoding --- packages/world-modules/test/ERC721.t.sol | 58 ++++++++++++------------ 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/packages/world-modules/test/ERC721.t.sol b/packages/world-modules/test/ERC721.t.sol index 2c451f093a..e322bc1d6e 100644 --- a/packages/world-modules/test/ERC721.t.sol +++ b/packages/world-modules/test/ERC721.t.sol @@ -52,7 +52,7 @@ contract ERC721Recipient is ERC721TokenReceiver { contract RevertingERC721Recipient is ERC721TokenReceiver { function onERC721Received(address, address, uint256, bytes calldata) public virtual override returns (bytes4) { - revert(string(abi.encodePacked(ERC721TokenReceiver.onERC721Received.selector))); + revert(string(abi.encodeWithSelector(ERC721TokenReceiver.onERC721Received.selector))); } } @@ -83,7 +83,7 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { function _expectAccessDenied(address caller) internal { ResourceId tokenSystemId = _erc721SystemId("myERC721"); - vm.expectRevert(abi.encodePacked(IWorldErrors.World_AccessDenied.selector, tokenSystemId.toString(), caller)); + vm.expectRevert(abi.encodeWithSelector(IWorldErrors.World_AccessDenied.selector, tokenSystemId.toString(), caller)); } function _expectMintEvent(address to, uint256 id) internal { @@ -158,7 +158,7 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { assertEq(token.balanceOf(owner), 0); - vm.expectRevert(abi.encodePacked(ERC721NonexistentToken.selector, id)); + vm.expectRevert(abi.encodeWithSelector(ERC721NonexistentToken.selector, id)); token.ownerOf(id); } @@ -191,6 +191,7 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { token.mint(owner, id); + vm.prank(owner); _expectApprovalEvent(owner, spender, id); token.approve(spender, id); assertEq(token.getApproved(id), spender); @@ -210,10 +211,10 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { token.transferFrom(owner, address(0), id); assertEq(token.balanceOf(owner), 0); - vm.expectRevert(abi.encodePacked(ERC721NonexistentToken.selector, id)); + vm.expectRevert(abi.encodeWithSelector(ERC721NonexistentToken.selector, id)); token.getApproved(id); - vm.expectRevert(abi.encodePacked(ERC721NonexistentToken.selector, id)); + vm.expectRevert(abi.encodeWithSelector(ERC721NonexistentToken.selector, id)); token.ownerOf(id); // Mint again, expect approval to be cleared @@ -224,6 +225,7 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { function testApproveAll(address owner, address operator, bool approved) public { _assumeDifferentNonZero(owner, operator); + vm.prank(owner); _expectApprovalForAllEvent(owner, operator, approved); token.setApprovalForAll(operator, approved); assertEq(token.isApprovedForAll(owner, operator), approved); @@ -368,7 +370,7 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { } function testMintToZeroReverts(uint256 id) public { - vm.expectRevert(abi.encodePacked(ERC721InvalidReceiver.selector, address(0))); + vm.expectRevert(abi.encodeWithSelector(ERC721InvalidReceiver.selector, address(0))); token.mint(address(0), id); } @@ -376,12 +378,12 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { vm.assume(to != address(0)); token.mint(to, id); - vm.expectRevert(abi.encodePacked(ERC721InvalidSender.selector, address(0))); + vm.expectRevert(abi.encodeWithSelector(ERC721InvalidSender.selector, address(0))); token.mint(to, id); } function testBurnNonExistentReverts(uint256 id) public { - vm.expectRevert(abi.encodePacked(ERC721NonexistentToken.selector, id)); + vm.expectRevert(abi.encodeWithSelector(ERC721NonexistentToken.selector, id)); token.burn(id); } @@ -392,12 +394,12 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { token.burn(id); - vm.expectRevert(abi.encodePacked(ERC721NonexistentToken.selector, id)); + vm.expectRevert(abi.encodeWithSelector(ERC721NonexistentToken.selector, id)); token.burn(id); } function testApproveNonExistentReverts(uint256 id, address to) public { - vm.expectRevert(abi.encodePacked(ERC721NonexistentToken.selector, id)); + vm.expectRevert(abi.encodeWithSelector(ERC721NonexistentToken.selector, id)); token.approve(to, id); } @@ -406,13 +408,13 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { token.mint(owner, id); - vm.expectRevert(abi.encodePacked(ERC721InvalidApprover.selector, operator)); + vm.expectRevert(abi.encodeWithSelector(ERC721InvalidApprover.selector, operator)); vm.prank(operator); token.approve(to, id); } function testTransferFromNotExistentReverts(address from, address to, uint256 id) public { - vm.expectRevert(abi.encodePacked(ERC721NonexistentToken.selector, id)); + vm.expectRevert(abi.encodeWithSelector(ERC721NonexistentToken.selector, id)); token.transferFrom(from, to, id); } @@ -420,14 +422,14 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { _assumeDifferentNonZero(owner, from, to); token.mint(owner, id); - vm.expectRevert(abi.encodePacked(ERC721IncorrectOwner.selector, from, id, owner)); + vm.expectRevert(abi.encodeWithSelector(ERC721IncorrectOwner.selector, from, id, owner)); token.transferFrom(from, to, id); } function testTransferFromToZeroReverts(uint256 id) public { token.mint(address(this), id); - vm.expectRevert(abi.encodePacked(ERC721InvalidReceiver.selector, address(0))); + vm.expectRevert(abi.encodeWithSelector(ERC721InvalidReceiver.selector, address(0))); token.transferFrom(address(this), address(0), id); } @@ -437,7 +439,7 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { token.mint(from, id); vm.prank(operator); - vm.expectRevert(abi.encodePacked(ERC721InsufficientApproval.selector, operator, id)); + vm.expectRevert(abi.encodeWithSelector(ERC721InsufficientApproval.selector, operator, id)); token.transferFrom(from, to, id); } @@ -449,7 +451,7 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { address to = address(new NonERC721Recipient()); vm.prank(from); - vm.expectRevert(abi.encodePacked(ERC721InvalidReceiver.selector, to)); + vm.expectRevert(abi.encodeWithSelector(ERC721InvalidReceiver.selector, to)); token.safeTransferFrom(from, to, id); } @@ -461,7 +463,7 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { address to = address(new NonERC721Recipient()); vm.prank(from); - vm.expectRevert(abi.encodePacked(ERC721InvalidReceiver.selector, to)); + vm.expectRevert(abi.encodeWithSelector(ERC721InvalidReceiver.selector, to)); token.safeTransferFrom(from, to, id, data); } @@ -473,7 +475,7 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { address to = address(new RevertingERC721Recipient()); vm.prank(from); - vm.expectRevert(abi.encodePacked(ERC721InvalidReceiver.selector, to)); + vm.expectRevert(abi.encodeWithSelector(ERC721InvalidReceiver.selector, to)); token.safeTransferFrom(from, to, id); } @@ -489,7 +491,7 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { address to = address(new RevertingERC721Recipient()); vm.prank(from); - vm.expectRevert(abi.encodePacked(ERC721TokenReceiver.onERC721Received.selector)); + vm.expectRevert(abi.encodeWithSelector(ERC721TokenReceiver.onERC721Received.selector)); token.safeTransferFrom(from, to, id, data); } @@ -501,7 +503,7 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { address to = address(new WrongReturnDataERC721Recipient()); vm.prank(from); - vm.expectRevert(abi.encodePacked(ERC721InvalidReceiver.selector, to)); + vm.expectRevert(abi.encodeWithSelector(ERC721InvalidReceiver.selector, to)); token.safeTransferFrom(from, to, id); } @@ -517,54 +519,54 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { address to = address(new WrongReturnDataERC721Recipient()); vm.prank(from); - vm.expectRevert(abi.encodePacked(ERC721InvalidReceiver.selector, to)); + vm.expectRevert(abi.encodeWithSelector(ERC721InvalidReceiver.selector, to)); token.safeTransferFrom(from, to, id, data); } function testSafeMintToNonERC721RecipientReverts(uint256 id) public { address to = address(new NonERC721Recipient()); - vm.expectRevert(abi.encodePacked(ERC721InvalidReceiver.selector, to)); + vm.expectRevert(abi.encodeWithSelector(ERC721InvalidReceiver.selector, to)); token.safeMint(to, id); } function testSafeMintToNonERC721RecipientWithDataReverts(uint256 id, bytes memory data) public { address to = address(new NonERC721Recipient()); - vm.expectRevert(abi.encodePacked(ERC721InvalidReceiver.selector, to)); + vm.expectRevert(abi.encodeWithSelector(ERC721InvalidReceiver.selector, to)); token.safeMint(to, id, data); } function testSafeMintToRevertingERC721RecipientReverts(uint256 id) public { address to = address(new RevertingERC721Recipient()); - vm.expectRevert(abi.encodePacked(ERC721TokenReceiver.onERC721Received.selector)); + vm.expectRevert(abi.encodeWithSelector(ERC721TokenReceiver.onERC721Received.selector)); token.safeMint(to, id); } function testSafeMintToRevertingERC721RecipientWithDataReverts(uint256 id, bytes memory data) public { address to = address(new RevertingERC721Recipient()); - vm.expectRevert(abi.encodePacked(ERC721TokenReceiver.onERC721Received.selector)); + vm.expectRevert(abi.encodeWithSelector(ERC721TokenReceiver.onERC721Received.selector)); token.safeMint(to, id, data); } function testSafeMintToERC721RecipientWithWrongReturnData(uint256 id) public { address to = address(new WrongReturnDataERC721Recipient()); - vm.expectRevert(abi.encodePacked(ERC721InvalidReceiver.selector, to)); + vm.expectRevert(abi.encodeWithSelector(ERC721InvalidReceiver.selector, to)); token.safeMint(to, id); } function testSafeMintToERC721RecipientWithWrongReturnDataWithData(uint256 id, bytes memory data) public { address to = address(new WrongReturnDataERC721Recipient()); - vm.expectRevert(abi.encodePacked(ERC721InvalidReceiver.selector, to)); + vm.expectRevert(abi.encodeWithSelector(ERC721InvalidReceiver.selector, to)); token.safeMint(to, id, data); } function testOwnerOfNonExistent(uint256 id) public { - vm.expectRevert(abi.encodePacked(ERC721NonexistentToken.selector, id)); + vm.expectRevert(abi.encodeWithSelector(ERC721NonexistentToken.selector, id)); token.ownerOf(id); } } From 5f966b3b7861a4be62eb235a4c7b4998a5ab2a41 Mon Sep 17 00:00:00 2001 From: alvrs Date: Mon, 30 Oct 2023 13:50:25 +0100 Subject: [PATCH 14/27] fix bug in _update --- .../src/modules/erc721-puppet/ERC721System.sol | 2 +- packages/world-modules/test/ERC721.t.sol | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/world-modules/src/modules/erc721-puppet/ERC721System.sol b/packages/world-modules/src/modules/erc721-puppet/ERC721System.sol index 37b4adf754..2acab23b0b 100644 --- a/packages/world-modules/src/modules/erc721-puppet/ERC721System.sol +++ b/packages/world-modules/src/modules/erc721-puppet/ERC721System.sol @@ -299,7 +299,7 @@ contract ERC721System is IERC721Mintable, System, PuppetMaster { if (to != address(0)) { unchecked { - Balances.set(balanceTableId, from, Balances.get(balanceTableId, from) + 1); + Balances.set(balanceTableId, to, Balances.get(balanceTableId, to) + 1); } } diff --git a/packages/world-modules/test/ERC721.t.sol b/packages/world-modules/test/ERC721.t.sol index e322bc1d6e..1f6c13fcd4 100644 --- a/packages/world-modules/test/ERC721.t.sol +++ b/packages/world-modules/test/ERC721.t.sol @@ -150,13 +150,17 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { function testBurn(uint256 id, address owner) public { vm.assume(owner != address(0)); + assertEq(token.balanceOf(owner), 0, "before"); + _expectMintEvent(owner, id); token.mint(owner, id); + assertEq(token.balanceOf(owner), 1, "after mint"); + _expectBurnEvent(owner, id); token.burn(id); - assertEq(token.balanceOf(owner), 0); + assertEq(token.balanceOf(owner), 0, "after burn"); vm.expectRevert(abi.encodeWithSelector(ERC721NonexistentToken.selector, id)); token.ownerOf(id); @@ -168,7 +172,7 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { _expectMintEvent(owner, id); token.mint(owner, id); - _expectAccessDenied(address(this)); + _expectAccessDenied(operator); vm.prank(operator); token.burn(id); } From 32adab0f69e7849e9edf6008396f353d5e3acca7 Mon Sep 17 00:00:00 2001 From: alvrs Date: Mon, 30 Oct 2023 13:51:32 +0100 Subject: [PATCH 15/27] fix assumption in testSafeTransferFromToEOA --- packages/world-modules/test/ERC721.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/world-modules/test/ERC721.t.sol b/packages/world-modules/test/ERC721.t.sol index 1f6c13fcd4..575092ab86 100644 --- a/packages/world-modules/test/ERC721.t.sol +++ b/packages/world-modules/test/ERC721.t.sol @@ -267,7 +267,7 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { } function testSafeTransferFromToEOA(uint256 id, address from, address to, address operator) public { - _assumeDifferentNonZero(from, to); + _assumeDifferentNonZero(from, to, operator); token.mint(from, id); From c1cb9c730492fb07edf6605c409cb7f1eac378dc Mon Sep 17 00:00:00 2001 From: alvrs Date: Mon, 30 Oct 2023 13:54:24 +0100 Subject: [PATCH 16/27] fix more assumptions' : --- packages/world-modules/test/ERC721.t.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/world-modules/test/ERC721.t.sol b/packages/world-modules/test/ERC721.t.sol index 575092ab86..5c32eb1aa0 100644 --- a/packages/world-modules/test/ERC721.t.sol +++ b/packages/world-modules/test/ERC721.t.sol @@ -250,7 +250,7 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { } function testTransferFromApproveAll(uint256 id, address from, address to, address operator) public { - _assumeDifferentNonZero(from, to); + _assumeDifferentNonZero(from, to, operator); token.mint(from, id); @@ -418,6 +418,8 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { } function testTransferFromNotExistentReverts(address from, address to, uint256 id) public { + _assumeDifferentNonZero(from, to); + vm.expectRevert(abi.encodeWithSelector(ERC721NonexistentToken.selector, id)); token.transferFrom(from, to, id); } From b06b27f2ca55e40e7a1b05163ed4a2223fbd5471 Mon Sep 17 00:00:00 2001 From: alvrs Date: Mon, 30 Oct 2023 14:35:26 +0100 Subject: [PATCH 17/27] fix expected error --- packages/world-modules/test/ERC721.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/world-modules/test/ERC721.t.sol b/packages/world-modules/test/ERC721.t.sol index 5c32eb1aa0..95f164040a 100644 --- a/packages/world-modules/test/ERC721.t.sol +++ b/packages/world-modules/test/ERC721.t.sol @@ -481,7 +481,7 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { address to = address(new RevertingERC721Recipient()); vm.prank(from); - vm.expectRevert(abi.encodeWithSelector(ERC721InvalidReceiver.selector, to)); + vm.expectRevert(abi.encodeWithSelector(ERC721TokenReceiver.onERC721Received.selector)); token.safeTransferFrom(from, to, id); } From 601af88b0e1bec922806329e1a0903c39e4c11de Mon Sep 17 00:00:00 2001 From: alvrs Date: Mon, 30 Oct 2023 15:28:23 +0100 Subject: [PATCH 18/27] fix test --- packages/world-modules/test/ERC721.t.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/world-modules/test/ERC721.t.sol b/packages/world-modules/test/ERC721.t.sol index 95f164040a..0f4611a9d4 100644 --- a/packages/world-modules/test/ERC721.t.sol +++ b/packages/world-modules/test/ERC721.t.sol @@ -428,6 +428,7 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { _assumeDifferentNonZero(owner, from, to); token.mint(owner, id); + vm.prank(owner); vm.expectRevert(abi.encodeWithSelector(ERC721IncorrectOwner.selector, from, id, owner)); token.transferFrom(from, to, id); } From 8d97dd42c1dc7907f1c6f2ceb2e8692c6ee51abc Mon Sep 17 00:00:00 2001 From: alvrs Date: Mon, 30 Oct 2023 20:56:50 +0100 Subject: [PATCH 19/27] assume eoa in eoa tests --- packages/world-modules/test/ERC721.t.sol | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/world-modules/test/ERC721.t.sol b/packages/world-modules/test/ERC721.t.sol index 0f4611a9d4..a191aeddb0 100644 --- a/packages/world-modules/test/ERC721.t.sol +++ b/packages/world-modules/test/ERC721.t.sol @@ -115,6 +115,14 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { vm.assume(address1 != address2); } + function _assumeEOA(address address1) internal view { + uint256 toCodeSize; + assembly { + toCodeSize := extcodesize(address1) + } + vm.assume(toCodeSize == 0); + } + function _assumeDifferentNonZero(address address1, address address2, address address3) internal pure { vm.assume(address1 != address(0)); vm.assume(address2 != address(0)); @@ -267,6 +275,8 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { } function testSafeTransferFromToEOA(uint256 id, address from, address to, address operator) public { + _assumeEOA(from); + _assumeEOA(to); _assumeDifferentNonZero(from, to, operator); token.mint(from, id); @@ -337,6 +347,7 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { } function testSafeMintToEOA(uint256 id, address to) public { + _assumeEOA(to); vm.assume(to != address(0)); token.safeMint(to, id); From d9d2c013fa70ab3b4ef3d4664196a187484218a5 Mon Sep 17 00:00:00 2001 From: alvrs Date: Mon, 30 Oct 2023 21:12:21 +0100 Subject: [PATCH 20/27] only allow token owner to burn (as per spec) --- .../modules/erc721-puppet/ERC721System.sol | 6 ----- packages/world-modules/test/ERC721.t.sol | 25 ------------------- 2 files changed, 31 deletions(-) diff --git a/packages/world-modules/src/modules/erc721-puppet/ERC721System.sol b/packages/world-modules/src/modules/erc721-puppet/ERC721System.sol index 2acab23b0b..8eb711a3ef 100644 --- a/packages/world-modules/src/modules/erc721-puppet/ERC721System.sol +++ b/packages/world-modules/src/modules/erc721-puppet/ERC721System.sol @@ -24,12 +24,6 @@ import { TokenURI } from "./tables/TokenURI.sol"; import { _balancesTableId, _metadataTableId, _tokenUriTableId, _operatorApprovalTableId, _ownersTableId, _tokenApprovalTableId, _toBytes32 } from "./utils.sol"; -/** - * TODO: - * - Add ERC721 tests - * - TODO: allow burn to be called by owner of token - */ - contract ERC721System is IERC721Mintable, System, PuppetMaster { using WorldResourceIdInstance for ResourceId; diff --git a/packages/world-modules/test/ERC721.t.sol b/packages/world-modules/test/ERC721.t.sol index a191aeddb0..22078401b2 100644 --- a/packages/world-modules/test/ERC721.t.sol +++ b/packages/world-modules/test/ERC721.t.sol @@ -209,31 +209,6 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { assertEq(token.getApproved(id), spender); } - function testApproveBurn(address owner, uint256 id, address spender) public { - _assumeDifferentNonZero(owner, spender); - - token.mint(owner, id); - - vm.prank(owner); - token.approve(spender, id); - - // Burn by sending to 0 address - // TODO: this currently fails, but we should allow burning if approved - vm.prank(owner); - token.transferFrom(owner, address(0), id); - assertEq(token.balanceOf(owner), 0); - - vm.expectRevert(abi.encodeWithSelector(ERC721NonexistentToken.selector, id)); - token.getApproved(id); - - vm.expectRevert(abi.encodeWithSelector(ERC721NonexistentToken.selector, id)); - token.ownerOf(id); - - // Mint again, expect approval to be cleared - token.mint(owner, id); - assertEq(token.getApproved(id), address(0)); - } - function testApproveAll(address owner, address operator, bool approved) public { _assumeDifferentNonZero(owner, operator); From b01a5c446f31a181291b3078999ea36ad159a335 Mon Sep 17 00:00:00 2001 From: alvrs Date: Mon, 30 Oct 2023 21:18:52 +0100 Subject: [PATCH 21/27] add test for installing twice --- packages/world-modules/test/ERC721.t.sol | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/packages/world-modules/test/ERC721.t.sol b/packages/world-modules/test/ERC721.t.sol index 22078401b2..17bf5595af 100644 --- a/packages/world-modules/test/ERC721.t.sol +++ b/packages/world-modules/test/ERC721.t.sol @@ -137,6 +137,22 @@ contract ERC721Test is Test, GasReporter, IERC721Events, IERC721Errors { assertEq(NamespaceOwner.get(WorldResourceIdLib.encodeNamespace("myERC721")), address(this)); } + function testInstallTwice() public { + // Install the ERC721 module again + IERC721Mintable anotherToken = registerERC721( + world, + "anotherERC721", + ERC721MetadataData({ name: "Token", symbol: "TKN", baseURI: "" }) + ); + assertTrue(address(anotherToken) != address(0)); + assertTrue(address(anotherToken) != address(token)); + } + + ///////////////////////////////////////////////// + // SOLADY ERC721 TEST CAES + // (https://github.com/Vectorized/solady/blob/main/test/ERC721.t.sol) + ///////////////////////////////////////////////// + function testMint(uint256 id, address owner) public { vm.assume(owner != address(0)); From 56fd216c91de58ba8c1d8eb22d6a206efdab0cb0 Mon Sep 17 00:00:00 2001 From: alvrs Date: Mon, 30 Oct 2023 21:20:50 +0100 Subject: [PATCH 22/27] remove unused imports --- .../world-modules/src/modules/erc721-puppet/ERC721System.sol | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/world-modules/src/modules/erc721-puppet/ERC721System.sol b/packages/world-modules/src/modules/erc721-puppet/ERC721System.sol index 8eb711a3ef..0a068cfa97 100644 --- a/packages/world-modules/src/modules/erc721-puppet/ERC721System.sol +++ b/packages/world-modules/src/modules/erc721-puppet/ERC721System.sol @@ -2,12 +2,9 @@ pragma solidity >=0.8.21; import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; -import { RESOURCE_TABLE } from "@latticexyz/store/src/storeResourceTypes.sol"; import { System } from "@latticexyz/world/src/System.sol"; import { WorldResourceIdInstance, WorldResourceIdLib } 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 { WorldContextConsumer } from "@latticexyz/world/src/WorldContext.sol"; import { AccessControlLib } from "../../utils/AccessControlLib.sol"; import { PuppetMaster } from "../puppet/PuppetMaster.sol"; From 3e852bb8a05e1e81a5fc9bb6ac2e5604ff2da331 Mon Sep 17 00:00:00 2001 From: alvrs Date: Mon, 30 Oct 2023 21:23:07 +0100 Subject: [PATCH 23/27] remove more unused imports --- .../world-modules/src/modules/erc721-puppet/ERC721System.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/world-modules/src/modules/erc721-puppet/ERC721System.sol b/packages/world-modules/src/modules/erc721-puppet/ERC721System.sol index 0a068cfa97..431258d99e 100644 --- a/packages/world-modules/src/modules/erc721-puppet/ERC721System.sol +++ b/packages/world-modules/src/modules/erc721-puppet/ERC721System.sol @@ -3,7 +3,7 @@ pragma solidity >=0.8.21; import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; import { System } from "@latticexyz/world/src/System.sol"; -import { WorldResourceIdInstance, WorldResourceIdLib } from "@latticexyz/world/src/WorldResourceId.sol"; +import { WorldResourceIdInstance } from "@latticexyz/world/src/WorldResourceId.sol"; import { SystemRegistry } from "@latticexyz/world/src/codegen/tables/SystemRegistry.sol"; import { AccessControlLib } from "../../utils/AccessControlLib.sol"; From 40b0210b25b57c34bdd56df7a8aac9f2586af35a Mon Sep 17 00:00:00 2001 From: alvarius Date: Mon, 30 Oct 2023 21:51:44 +0100 Subject: [PATCH 24/27] Create gorgeous-swans-hide.md --- .changeset/gorgeous-swans-hide.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .changeset/gorgeous-swans-hide.md diff --git a/.changeset/gorgeous-swans-hide.md b/.changeset/gorgeous-swans-hide.md new file mode 100644 index 0000000000..4835c5dd7d --- /dev/null +++ b/.changeset/gorgeous-swans-hide.md @@ -0,0 +1,20 @@ +--- +"@latticexyz/world-modules": minor +--- + +Adds 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: "" }));``` From f20ad7dfdf3193f9a248d54f8f43cffb18c9fc15 Mon Sep 17 00:00:00 2001 From: alvrs Date: Tue, 31 Oct 2023 22:39:57 +0100 Subject: [PATCH 25/27] use toTopic --- .../src/modules/erc721-puppet/ERC721System.sol | 9 +++++---- .../world-modules/src/modules/erc721-puppet/utils.sol | 8 -------- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/packages/world-modules/src/modules/erc721-puppet/ERC721System.sol b/packages/world-modules/src/modules/erc721-puppet/ERC721System.sol index 431258d99e..b4ce186ef8 100644 --- a/packages/world-modules/src/modules/erc721-puppet/ERC721System.sol +++ b/packages/world-modules/src/modules/erc721-puppet/ERC721System.sol @@ -8,6 +8,7 @@ import { SystemRegistry } from "@latticexyz/world/src/codegen/tables/SystemRegis 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 { IERC721Mintable } from "./IERC721Mintable.sol"; @@ -19,7 +20,7 @@ import { Owners } from "./tables/Owners.sol"; import { TokenApproval } from "./tables/TokenApproval.sol"; import { TokenURI } from "./tables/TokenURI.sol"; -import { _balancesTableId, _metadataTableId, _tokenUriTableId, _operatorApprovalTableId, _ownersTableId, _tokenApprovalTableId, _toBytes32 } from "./utils.sol"; +import { _balancesTableId, _metadataTableId, _tokenUriTableId, _operatorApprovalTableId, _ownersTableId, _tokenApprovalTableId } from "./utils.sol"; contract ERC721System is IERC721Mintable, System, PuppetMaster { using WorldResourceIdInstance for ResourceId; @@ -297,7 +298,7 @@ contract ERC721System is IERC721Mintable, System, PuppetMaster { Owners.set(_ownersTableId(_namespace()), tokenId, to); // Emit Transfer event on puppet - puppet().log(Transfer.selector, _toBytes32(from), _toBytes32(to), _toBytes32(tokenId), new bytes(0)); + puppet().log(Transfer.selector, toTopic(from), toTopic(to), toTopic(tokenId), new bytes(0)); return from; } @@ -450,7 +451,7 @@ contract ERC721System is IERC721Mintable, System, PuppetMaster { if (emitEvent) { // Emit Approval event on puppet - puppet().log(Approval.selector, _toBytes32(owner), _toBytes32(to), _toBytes32(tokenId), new bytes(0)); + puppet().log(Approval.selector, toTopic(owner), toTopic(to), toTopic(tokenId), new bytes(0)); } } @@ -472,7 +473,7 @@ contract ERC721System is IERC721Mintable, System, PuppetMaster { OperatorApproval.set(_operatorApprovalTableId(_namespace()), owner, operator, approved); // Emit ApprovalForAll event on puppet - puppet().log(ApprovalForAll.selector, _toBytes32(owner), _toBytes32(operator), abi.encode(approved)); + puppet().log(ApprovalForAll.selector, toTopic(owner), toTopic(operator), abi.encode(approved)); } /** diff --git a/packages/world-modules/src/modules/erc721-puppet/utils.sol b/packages/world-modules/src/modules/erc721-puppet/utils.sol index b093caa559..d7cdd6156a 100644 --- a/packages/world-modules/src/modules/erc721-puppet/utils.sol +++ b/packages/world-modules/src/modules/erc721-puppet/utils.sol @@ -33,14 +33,6 @@ function _tokenUriTableId(bytes14 namespace) pure returns (ResourceId) { return WorldResourceIdLib.encode({ typeId: RESOURCE_TABLE, namespace: namespace, name: TOKEN_URI_NAME }); } -function _toBytes32(address addr) pure returns (bytes32) { - return bytes32(uint256(uint160(addr))); -} - -function _toBytes32(uint256 id) pure returns (bytes32) { - return bytes32(id); -} - function _erc721SystemId(bytes14 namespace) pure returns (ResourceId) { return WorldResourceIdLib.encode({ typeId: RESOURCE_SYSTEM, namespace: namespace, name: ERC721_SYSTEM_NAME }); } From d963bb4c01f4ad312128f8468609821c76fd2271 Mon Sep 17 00:00:00 2001 From: alvarius Date: Tue, 31 Oct 2023 22:47:39 +0100 Subject: [PATCH 26/27] Update .changeset/gorgeous-swans-hide.md --- .changeset/gorgeous-swans-hide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/gorgeous-swans-hide.md b/.changeset/gorgeous-swans-hide.md index 4835c5dd7d..55bee1866d 100644 --- a/.changeset/gorgeous-swans-hide.md +++ b/.changeset/gorgeous-swans-hide.md @@ -2,7 +2,7 @@ "@latticexyz/world-modules": minor --- -Adds the `ERC721Module` to `@latticexyz/world-modules`. +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. From 65a8802195a6a5b02c20055c42709231f92b638e Mon Sep 17 00:00:00 2001 From: alvrs Date: Wed, 1 Nov 2023 15:07:34 +0100 Subject: [PATCH 27/27] prettier --- .changeset/gorgeous-swans-hide.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.changeset/gorgeous-swans-hide.md b/.changeset/gorgeous-swans-hide.md index 55bee1866d..152184065c 100644 --- a/.changeset/gorgeous-swans-hide.md +++ b/.changeset/gorgeous-swans-hide.md @@ -2,12 +2,12 @@ "@latticexyz/world-modules": minor --- -Added the `ERC721Module` to `@latticexyz/world-modules`. +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 +````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"; @@ -18,3 +18,4 @@ 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: "" }));``` +````