diff --git a/contracts/ERC721M.sol b/contracts/ERC721M.sol index 58b29217..077f2e12 100644 --- a/contracts/ERC721M.sol +++ b/contracts/ERC721M.sol @@ -94,14 +94,6 @@ contract ERC721M is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard { _; } - /** - * @dev Returns whether NOT mintable. - */ - modifier cannotMint() { - if (_mintable) revert Mintable(); - _; - } - /** * @dev Returns whether it has enough supply for the given qty. */ @@ -110,13 +102,6 @@ contract ERC721M is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard { _; } - /** - * @dev Returns cosigner address. - */ - function getCosigner() external view override returns (address) { - return _cosigner; - } - /** * @dev Returns cosign nonce. */ @@ -132,13 +117,6 @@ contract ERC721M is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard { emit SetCosigner(cosigner); } - /** - * @dev Returns expiry in seconds. - */ - function getTimestampExpirySeconds() public view override returns (uint64) { - return _timestampExpirySeconds; - } - /** * @dev Sets expiry in seconds. This timestamp specifies how long a signature from cosigner is valid for. */ @@ -147,13 +125,6 @@ contract ERC721M is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard { emit SetTimestampExpirySeconds(expiry); } - /** - * @dev Returns crossmint address. - */ - function getCrossmintAddress() external view override returns (address) { - return _crossmintAddress; - } - /** * @dev Sets crossmint address if using crossmint. This allows the specified address to call `crossmint`. */ @@ -191,12 +162,11 @@ contract ERC721M is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard { _mintStages.pop(); } - uint64 timestampExpirySeconds = getTimestampExpirySeconds(); for (uint256 i = 0; i < newStages.length; i++) { if (i >= 1) { if ( newStages[i].startTimeUnixSeconds < - newStages[i - 1].endTimeUnixSeconds + timestampExpirySeconds + newStages[i - 1].endTimeUnixSeconds + _timestampExpirySeconds ) { revert InsufficientStageTimeGap(); } @@ -230,7 +200,7 @@ contract ERC721M is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard { /** * @dev Gets whether mintable. */ - function getMintable() external view override returns (bool) { + function getMintable() external view returns (bool) { return _mintable; } @@ -344,7 +314,7 @@ contract ERC721M is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard { if ( startTimeUnixSeconds < _mintStages[index - 1].endTimeUnixSeconds + - getTimestampExpirySeconds() + _timestampExpirySeconds ) { revert InsufficientStageTimeGap(); } @@ -374,7 +344,7 @@ contract ERC721M is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard { /** * @dev Returns mint currency address. */ - function getMintCurrency() external view override returns (address) { + function getMintCurrency() external view returns (address) { return _mintCurrency; } @@ -535,18 +505,6 @@ contract ERC721M is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard { emit PermanentBaseURI(_currentBaseURI); } - /** - * @dev Returns token URI suffix. - */ - function getTokenURISuffix() - external - view - override - returns (string memory) - { - return _tokenURISuffix; - } - /** * @dev Sets token URI suffix. e.g. ".json". */ @@ -609,7 +567,7 @@ contract ERC721M is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard { uint32 qty, uint64 timestamp, bytes memory signature - ) public view override { + ) public view { if ( !SignatureChecker.isValidSignatureNow( _cosigner, @@ -625,7 +583,6 @@ contract ERC721M is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard { function getActiveStageFromTimestamp(uint64 timestamp) public view - override returns (uint256) { for (uint256 i = 0; i < _mintStages.length; i++) { @@ -643,7 +600,7 @@ contract ERC721M is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard { * @dev Validates the timestamp is not expired. */ function _assertValidTimestamp(uint64 timestamp) internal view { - if (timestamp < block.timestamp - getTimestampExpirySeconds()) + if (timestamp < block.timestamp - _timestampExpirySeconds) revert TimestampExpired(); } diff --git a/contracts/ERC721MLite.sol b/contracts/ERC721MLite.sol index 1608389e..1b54fb5c 100644 --- a/contracts/ERC721MLite.sol +++ b/contracts/ERC721MLite.sol @@ -2,24 +2,29 @@ pragma solidity ^0.8.4; -import "@openzeppelin/contracts/access/Ownable.sol"; -import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; -import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; -import "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; -import "erc721a/contracts/extensions/ERC721AQueryable.sol"; -import "./IERC721M.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol"; +import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import {MerkleProof} from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; +import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; +import {ERC721A, ERC721AQueryable, ERC721A__IERC721Receiver} from "erc721a/contracts/extensions/ERC721AQueryable.sol"; +import {IERC721A, IERC721M} from "./IERC721M.sol"; +import {DefaultOperatorFilterer} from "./OperatorFilter/DefaultOperatorFilterer.sol"; /** * @title ERC721MLite * - * @dev ERC721A subclass with MagicEden launchpad features including - * - multiple minting stages with time-based auto stage switch - * - global and stage wallet-level minting limit - * - whitelist using merkle tree - * - anti-botting with cosigner + * @dev Lite version of ERC721M, without the following features: + * - crossminting + * - update a speficic stage */ -contract ERC721MLite is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard { +contract ERC721MLite is + IERC721M, + ERC721AQueryable, + DefaultOperatorFilterer, + Ownable, + ReentrancyGuard +{ using ECDSA for bytes32; // Whether this contract is mintable. @@ -80,14 +85,6 @@ contract ERC721MLite is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard { _; } - /** - * @dev Returns whether NOT mintable. - */ - modifier cannotMint() { - if (_mintable) revert Mintable(); - _; - } - /** * @dev Returns whether it has enough supply for the given qty. */ @@ -96,20 +93,6 @@ contract ERC721MLite is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard { _; } - /** - * @dev Returns crossmint address. Always returns 0x0. - */ - function getCrossmintAddress() external pure override returns (address) { - return address(0); - } - - /** - * @dev Returns cosigner address. - */ - function getCosigner() external view override returns (address) { - return _cosigner; - } - /** * @dev Returns cosign nonce. */ @@ -117,13 +100,6 @@ contract ERC721MLite is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard { return _numberMinted(minter); } - /** - * @dev Returns expiry in seconds. - */ - function getTimestampExpirySeconds() public view override returns (uint64) { - return _timestampExpirySeconds; - } - /** * @dev Sets stages in the format of an array of `MintStageInfo`. * @@ -153,12 +129,12 @@ contract ERC721MLite is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard { _mintStages.pop(); } - uint64 timestampExpirySeconds = getTimestampExpirySeconds(); for (uint256 i = 0; i < newStages.length; i++) { if (i >= 1) { if ( newStages[i].startTimeUnixSeconds < - newStages[i - 1].endTimeUnixSeconds + timestampExpirySeconds + newStages[i - 1].endTimeUnixSeconds + + _timestampExpirySeconds ) { revert InsufficientStageTimeGap(); } @@ -189,13 +165,6 @@ contract ERC721MLite is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard { } } - /** - * @dev Gets whether mintable. - */ - function getMintable() external view override returns (bool) { - return _mintable; - } - /** * @dev Sets mintable. */ @@ -221,11 +190,9 @@ contract ERC721MLite is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard { /** * @dev Sets maximum mintable supply. */ - function setMaxMintableSupply(uint256 maxMintableSupply) - external - virtual - onlyOwner - { + function setMaxMintableSupply( + uint256 maxMintableSupply + ) external virtual onlyOwner { if (maxMintableSupply > _maxMintableSupply) { revert CannotIncreaseMaxMintableSupply(); } @@ -244,29 +211,18 @@ contract ERC721MLite is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard { /** * @dev Returns number of minted token for a given address. */ - function totalMintedByAddress(address a) - external - view - virtual - override - returns (uint256) - { + function totalMintedByAddress( + address a + ) external view virtual override returns (uint256) { return _numberMinted(a); } /** * @dev Returns info for one stage specified by index (starting from 0). */ - function getStageInfo(uint256 index) - external - view - override - returns ( - MintStageInfo memory, - uint32, - uint256 - ) - { + function getStageInfo( + uint256 index + ) external view override returns (MintStageInfo memory, uint32, uint256) { if (index >= _mintStages.length) { revert("InvalidStage"); } @@ -275,50 +231,6 @@ contract ERC721MLite is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard { return (_mintStages[index], walletMinted, stageMinted); } - /** - * @dev Updates info for one stage specified by index (starting from 0). - */ - function updateStage( - uint256 index, - uint80 price, - uint32 walletLimit, - bytes32 merkleRoot, - uint24 maxStageSupply, - uint64 startTimeUnixSeconds, - uint64 endTimeUnixSeconds - ) external onlyOwner { - if (index >= _mintStages.length) revert InvalidStage(); - if (index >= 1) { - if ( - startTimeUnixSeconds < - _mintStages[index - 1].endTimeUnixSeconds + - getTimestampExpirySeconds() - ) { - revert InsufficientStageTimeGap(); - } - } - _assertValidStartAndEndTimestamp( - startTimeUnixSeconds, - endTimeUnixSeconds - ); - _mintStages[index].price = price; - _mintStages[index].walletLimit = walletLimit; - _mintStages[index].merkleRoot = merkleRoot; - _mintStages[index].maxStageSupply = maxStageSupply; - _mintStages[index].startTimeUnixSeconds = startTimeUnixSeconds; - _mintStages[index].endTimeUnixSeconds = endTimeUnixSeconds; - - emit UpdateStage( - index, - price, - walletLimit, - merkleRoot, - maxStageSupply, - startTimeUnixSeconds, - endTimeUnixSeconds - ); - } - /** * @dev Mints token(s). * @@ -332,20 +244,7 @@ contract ERC721MLite is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard { bytes32[] calldata proof, uint64 timestamp, bytes calldata signature - ) virtual external payable nonReentrant { - _mintInternal(qty, msg.sender, proof, timestamp, signature); - } - - /** - * @dev Implementation of minting. - */ - function _mintInternal( - uint32 qty, - address to, - bytes32[] calldata proof, - uint64 timestamp, - bytes calldata signature - ) internal canMint hasSupply(qty) { + ) external payable virtual canMint hasSupply(qty) nonReentrant { uint64 stageTimestamp = uint64(block.timestamp); MintStageInfo memory stage; @@ -370,14 +269,14 @@ contract ERC721MLite is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard { // Check global wallet limit if applicable if (_globalWalletLimit > 0) { - if (_numberMinted(to) + qty > _globalWalletLimit) + if (_numberMinted(msg.sender) + qty > _globalWalletLimit) revert WalletGlobalLimitExceeded(); } // Check wallet limit for stage if applicable, limit == 0 means no limit enforced if (stage.walletLimit > 0) { if ( - _stageMintedCountsPerWallet[activeStage][to] + qty > + _stageMintedCountsPerWallet[activeStage][msg.sender] + qty > stage.walletLimit ) revert WalletStageLimitExceeded(); } @@ -387,14 +286,14 @@ contract ERC721MLite is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard { if ( MerkleProof.processProof( proof, - keccak256(abi.encodePacked(to)) + keccak256(abi.encodePacked(msg.sender)) ) != stage.merkleRoot ) revert InvalidProof(); } - _stageMintedCountsPerWallet[activeStage][to] += qty; + _stageMintedCountsPerWallet[activeStage][msg.sender] += qty; _stageMintedCounts[activeStage] += qty; - _safeMint(to, qty); + _safeMint(msg.sender, qty); } /** @@ -403,11 +302,10 @@ contract ERC721MLite is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard { * NOTE: This function bypasses validations thus only available for owner. * This is typically used for owner to pre-mint or mint the remaining of the supply. */ - function ownerMint(uint32 qty, address to) - external - onlyOwner - hasSupply(qty) - { + function ownerMint( + uint32 qty, + address to + ) external onlyOwner hasSupply(qty) { _safeMint(to, qty); } @@ -429,34 +327,12 @@ contract ERC721MLite is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard { emit SetBaseURI(baseURI); } - /** - * @dev Returns token URI suffix. - */ - function getTokenURISuffix() - external - view - override - returns (string memory) - { - return _tokenURISuffix; - } - - /** - * @dev Sets token URI suffix. e.g. ".json". - */ - function setTokenURISuffix(string calldata suffix) external onlyOwner { - _tokenURISuffix = suffix; - } - /** * @dev Returns token URI for a given token id. */ - function tokenURI(uint256 tokenId) - public - view - override(ERC721A, IERC721A) - returns (string memory) - { + function tokenURI( + uint256 tokenId + ) public view override(ERC721A, IERC721A) returns (string memory) { if (!_exists(tokenId)) revert URIQueryForNonexistentToken(); string memory baseURI = _currentBaseURI; @@ -516,12 +392,9 @@ contract ERC721MLite is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard { /** * @dev Returns the current active stage based on timestamp. */ - function getActiveStageFromTimestamp(uint64 timestamp) - public - view - override - returns (uint256) - { + function getActiveStageFromTimestamp( + uint64 timestamp + ) public view returns (uint256) { for (uint256 i = 0; i < _mintStages.length; i++) { if ( timestamp >= _mintStages[i].startTimeUnixSeconds && @@ -533,28 +406,21 @@ contract ERC721MLite is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard { revert InvalidStage(); } - /** - * @dev Returns mint currency address. - */ - function getMintCurrency() external pure override returns (address) { - return address(0); - } - /** * @dev Validates the timestamp is not expired. */ function _assertValidTimestamp(uint64 timestamp) internal view { - if (timestamp < block.timestamp - getTimestampExpirySeconds()) + if (timestamp < block.timestamp - _timestampExpirySeconds) revert TimestampExpired(); } /** * @dev Validates the start timestamp is before end timestamp. Used when updating stages. */ - function _assertValidStartAndEndTimestamp(uint64 start, uint64 end) - internal - pure - { + function _assertValidStartAndEndTimestamp( + uint64 start, + uint64 end + ) internal pure { if (start >= end) revert InvalidStartAndEndTimestamp(); } @@ -568,4 +434,29 @@ contract ERC721MLite is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard { } return chainID; } -} \ No newline at end of file + + function transferFrom( + address from, + address to, + uint256 tokenId + ) public payable override(ERC721A, IERC721A) onlyAllowedOperator(from) { + super.transferFrom(from, to, tokenId); + } + + function safeTransferFrom( + address from, + address to, + uint256 tokenId + ) public payable override(ERC721A, IERC721A) onlyAllowedOperator(from) { + super.safeTransferFrom(from, to, tokenId); + } + + function safeTransferFrom( + address from, + address to, + uint256 tokenId, + bytes memory data + ) public payable override(ERC721A, IERC721A) onlyAllowedOperator(from) { + super.safeTransferFrom(from, to, tokenId, data); + } +} diff --git a/contracts/ERC721MOnft.sol b/contracts/ERC721MOnft.sol index 6ae96d93..e9a759a7 100644 --- a/contracts/ERC721MOnft.sol +++ b/contracts/ERC721MOnft.sol @@ -11,7 +11,11 @@ import {IONFT721} from "@layerzerolabs/solidity-examples/contracts/token/onft/IO * * @dev ERC721MOnft is an ERC721M contract with LayerZero integration. */ -contract ERC721MOnft is ERC721MLite, ONFT721CoreLite, ERC721A__IERC721Receiver { +contract ERC721MOnft is + ERC721MLite, + ONFT721CoreLite, + ERC721A__IERC721Receiver +{ error CallerNotOwnerOrApproved(); error FromAddressNotOwner(); error NotExistOrNotOwnedByContract(); diff --git a/contracts/IERC721M.sol b/contracts/IERC721M.sol index 104c5976..bd99dfdb 100644 --- a/contracts/IERC721M.sol +++ b/contracts/IERC721M.sol @@ -59,24 +59,14 @@ interface IERC721M is IERC721AQueryable { event Withdraw(uint256 value); event WithdrawERC20(address mintCurrency, uint256 value); - function getCosigner() external view returns (address); - - function getCrossmintAddress() external view returns (address); - function getNumberStages() external view returns (uint256); function getGlobalWalletLimit() external view returns (uint256); - function getTimestampExpirySeconds() external view returns (uint64); - function getMaxMintableSupply() external view returns (uint256); - function getMintable() external view returns (bool); - function totalMintedByAddress(address a) external view returns (uint256); - function getTokenURISuffix() external view returns (string memory); - function getStageInfo(uint256 index) external view @@ -85,18 +75,4 @@ interface IERC721M is IERC721AQueryable { uint32, uint256 ); - - function getMintCurrency() external view returns (address); - - function getActiveStageFromTimestamp(uint64 timestamp) - external - view - returns (uint256); - - function assertValidCosign( - address minter, - uint32 qty, - uint64 timestamp, - bytes memory signature - ) external view; } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 093fdebe..e5d571ab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@magiceden-oss/erc721m", - "version": "0.0.10", + "version": "0.0.11", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@magiceden-oss/erc721m", - "version": "0.0.10", + "version": "0.0.11", "dependencies": { "@inquirer/prompts": "^2.2.0", "@layerzerolabs/solidity-examples": "^0.0.13", diff --git a/package.json b/package.json index 5a36b7ae..dead0a53 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@magiceden-oss/erc721m", - "version": "0.0.10", + "version": "0.0.11", "description": "erc721m contract for Solidity", "files": [ "/contracts/**/*.sol", diff --git a/test/ERC721MOnft.test.ts b/test/ERC721MOnft.test.ts index 78785364..80eb359b 100644 --- a/test/ERC721MOnft.test.ts +++ b/test/ERC721MOnft.test.ts @@ -3,7 +3,7 @@ import { Contract, Signer } from 'ethers'; import { ethers } from 'hardhat'; import { expect } from 'chai'; -describe.only('ERC721MOnft Test', () => { +describe('ERC721MOnft Test', () => { let contract: ERC721MOnft; let lzEndpoint: Contract; let owner: Signer; diff --git a/test/erc721m.test.ts b/test/erc721m.test.ts index 55073cad..6aa568d0 100644 --- a/test/erc721m.test.ts +++ b/test/erc721m.test.ts @@ -519,18 +519,6 @@ describe('ERC721M', function () { await expect(getStageInfo).to.be.revertedWith('InvalidStage'); }); - it('can set/get timestamp expiry', async () => { - expect((await contract.getTimestampExpirySeconds()).toNumber()).to.eq(60); - - await expect(contract.setTimestampExpirySeconds(120)) - .to.emit(contract, 'SetTimestampExpirySeconds') - .withArgs(120); - - expect((await contract.getTimestampExpirySeconds()).toNumber()).to.eq( - 120, - ); - }); - it('can find active stage', async () => { await contract.setStages([ { @@ -1318,10 +1306,6 @@ describe('ERC721M', function () { await ownerConn.setMintable(true); await ownerConn.setCrossmintAddress(crossmintAddressStr); - expect(await ownerConn.getCrossmintAddress()).to.equal( - crossmintAddressStr, - ); - // Impersonate Crossmint wallet const crossmintSigner = await ethers.getImpersonatedSigner( crossmintAddressStr, @@ -1389,10 +1373,6 @@ describe('ERC721M', function () { await ownerConn.setMintable(true); await ownerConn.setCrossmintAddress(crossmintAddressStr); - expect(await ownerConn.getCrossmintAddress()).to.equal( - crossmintAddressStr, - ); - // Impersonate Crossmint wallet const crossmintSigner = await ethers.getImpersonatedSigner( crossmintAddressStr, @@ -1788,9 +1768,6 @@ describe('ERC721M', function () { 'ipfs://bafybeidntqfipbuvdhdjosntmpxvxyse2dkyfpa635u4g6txruvt5qf7y4/', ); - const suffix = await contract.getTokenURISuffix(); - expect(suffix).to.equal('.json'); - const block = await ethers.provider.getBlock( await ethers.provider.getBlockNumber(), ); @@ -1837,14 +1814,12 @@ describe('ERC721M', function () { ); await erc721M.deployed(); const ownerConn = erc721M.connect(owner); - expect(await ownerConn.getCosigner()).to.eq(ethers.constants.AddressZero); await expect( ownerConn.getCosignDigest(owner.address, 1, 0), ).to.be.revertedWith('CosignerNotSet'); // we can set the cosigner await ownerConn.setCosigner(cosigner.address); - expect(await ownerConn.getCosigner()).to.eq(cosigner.address); // readonly contract can't set cosigner await expect( @@ -1868,8 +1843,6 @@ describe('ERC721M', function () { await erc721M.deployed(); const minterConn = erc721M.connect(minter); - expect(await minterConn.getCosigner()).to.eq(cosigner.address); - const timestamp = Math.floor(new Date().getTime() / 1000); const sig = await getCosignSignature( erc721M,