Skip to content

Commit

Permalink
updates
Browse files Browse the repository at this point in the history
Signed-off-by: Adam Wolf <[email protected]>
  • Loading branch information
wolfy-nft committed Dec 18, 2024
1 parent e5ac063 commit fd3fc3b
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 44 deletions.
2 changes: 1 addition & 1 deletion contracts/common/interfaces/IMagicDropMetadata.sol
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,4 @@ interface IMagicDropMetadata {
/// @param newReceiver The address that will receive future royalty payments
/// @param newBps The royalty percentage in basis points (e.g., 250 = 2.5%)
function setRoyaltyInfo(address newReceiver, uint96 newBps) external;
}
}
2 changes: 1 addition & 1 deletion contracts/nft/erc721m/clones/ERC721MagicDropCloneable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -375,4 +375,4 @@ contract ERC721MagicDropCloneable is ERC721MagicDropMetadataCloneable {
function _guardInitializeOwner() internal pure virtual override returns (bool) {
return true;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

import { IERC2981 } from "@openzeppelin/contracts/interfaces/IERC2981.sol";
import {IERC2981} from "@openzeppelin/contracts/interfaces/IERC2981.sol";

import {ERC2981} from "solady/src/tokens/ERC2981.sol";
import {Ownable} from "solady/src/auth/Ownable.sol";
Expand Down Expand Up @@ -229,4 +229,4 @@ contract ERC721MagicDropMetadataCloneable is
_contractURI = newContractURI;
emit ContractURIUpdated(newContractURI);
}
}
}
122 changes: 82 additions & 40 deletions test/erc721m/clones/ERC721MagicDropCloneable.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,45 +7,13 @@ import {console2} from "forge-std/console2.sol";
import {LibClone} from "solady/src/utils/LibClone.sol";
import {MerkleProofLib} from "solady/src/utils/MerkleProofLib.sol";

import {MerkleTestHelper} from "test/helpers/MerkleTestHelper.sol";

import {ERC721MagicDropCloneable} from "contracts/nft/erc721m/clones/ERC721MagicDropCloneable.sol";
import {IERC721MagicDropMetadata} from "contracts/nft/erc721m/interfaces/IERC721MagicDropMetadata.sol";
import {PublicStage, AllowlistStage, SetupConfig} from "contracts/nft/erc721m/clones/Types.sol";
import {IERC721MagicDropMetadata} from "contracts/nft/erc721m/interfaces/IERC721MagicDropMetadata.sol";

// Dummy merkle proof generation utilities for testing
contract MerkleTestHelper {
// This is a placeholder helper. In a real test, you'd generate a real merkle tree offline.
// Here we hardcode a single allowlisted address and its proof.
bytes32[] internal proof;
bytes32 internal root;
address internal allowedAddr;

constructor() {
allowedAddr = address(0xABCD);
// For simplicity, root = keccak256(abi.encodePacked(allowedAddr))
// Proof is empty since this is a single-leaf tree.
root = keccak256(abi.encodePacked(allowedAddr));
}

function getRoot() external view returns (bytes32) {
return root;
}

function getProofFor(address addr) external view returns (bytes32[] memory) {
if (addr == allowedAddr) {
// Single-leaf tree: no proof necessary except empty array
return new bytes32[](0);
} else {
// No valid proof
bytes32[] memory emptyProof;
return emptyProof;
}
}

function getAllowedAddress() external view returns (address) {
return allowedAddr;
}
}
import {IMagicDropMetadata} from "contracts/common/interfaces/IMagicDropMetadata.sol";

contract ERC721MagicDropCloneableTest is Test {
ERC721MagicDropCloneable public token;
Expand All @@ -54,6 +22,7 @@ contract ERC721MagicDropCloneableTest is Test {
address internal owner = address(0x1234);
address internal user = address(0x1111);
address internal user2 = address(0x2222);
address internal allowedAddr = address(0x3333);
address internal payoutRecipient = address(0x9999);
uint256 internal publicStart;
uint256 internal publicEnd;
Expand All @@ -62,7 +31,7 @@ contract ERC721MagicDropCloneableTest is Test {

function setUp() public {
token = ERC721MagicDropCloneable(LibClone.deployERC1967(address(new ERC721MagicDropCloneable())));
merkleHelper = new MerkleTestHelper();
merkleHelper = new MerkleTestHelper(allowedAddr);

// Initialize token
token.initialize("TestToken", "TT", owner);
Expand Down Expand Up @@ -170,6 +139,19 @@ contract ERC721MagicDropCloneableTest is Test {
vm.stopPrank();
}

function testMintPublicMaxSupplyExceededReverts() public {
vm.warp(publicStart + 1);
vm.deal(user, 11 ether);

vm.prank(owner);
// unlimited wallet limit for the purpose of this test
token.setWalletLimit(0);

vm.prank(user);
vm.expectRevert(IMagicDropMetadata.CannotExceedMaxSupply.selector);
token.mintPublic{value: 11 ether}(user, 1001);
}

/*==============================================================
= TEST ALLOWLIST MINTING STAGE =
==============================================================*/
Expand Down Expand Up @@ -242,6 +224,23 @@ contract ERC721MagicDropCloneableTest is Test {
vm.stopPrank();
}

function testMintAllowlistMaxSupplyExceededReverts() public {
// Move time to allowlist
vm.warp(allowlistStart + 1);

vm.prank(owner);
// unlimited wallet limit for the purpose of this test
token.setWalletLimit(0);

address allowedAddr = merkleHelper.getAllowedAddress();
bytes32[] memory proof = merkleHelper.getProofFor(allowedAddr);
vm.deal(allowedAddr, 11 ether);

vm.prank(allowedAddr);
vm.expectRevert(IMagicDropMetadata.CannotExceedMaxSupply.selector);
token.mintAllowlist{value: 11 ether}(allowedAddr, 1001, proof);
}

/*==============================================================
= BURNING =
==============================================================*/
Expand All @@ -254,7 +253,7 @@ contract ERC721MagicDropCloneableTest is Test {
vm.prank(user);
token.mintPublic{value: 0.01 ether}(user, 1);

uint256 tokenId = 1;
uint256 tokenId = 0;
assertEq(token.ownerOf(tokenId), user);

vm.prank(user);
Expand Down Expand Up @@ -427,22 +426,65 @@ contract ERC721MagicDropCloneableTest is Test {
assertEq(payoutRecipient.balance, initialPayoutBalance);
}

function testSplitProceedsPayoutRecipientZeroAddressReverts() public {
// Move to public sale time
vm.warp(publicStart + 1);

vm.prank(owner);
token.setPayoutRecipient(address(0));
assertEq(token.payoutRecipient(), address(0));

vm.deal(user, 1 ether);

vm.prank(user);
vm.expectRevert(ERC721MagicDropCloneable.PayoutRecipientCannotBeZeroAddress.selector);
token.mintPublic{value: 0.01 ether}(user, 1);
}

/*==============================================================
= METADATA =
==============================================================*/

function testTokenURI() public {
// Mint token #1
vm.warp(publicStart + 1);
vm.deal(user, 1 ether);
vm.prank(user);
token.mintPublic{value: 0.01 ether}(user, 1);
string memory uri = token.tokenURI(1);
assertEq(uri, "https://example.com/metadata/1");
string memory uri = token.tokenURI(0);
assertEq(uri, "https://example.com/metadata/0");
}

function testTokenURIWithEmptyBaseURI() public {
vm.warp(publicStart + 1);
vm.deal(user, 1 ether);
vm.prank(user);
token.mintPublic{value: 0.01 ether}(user, 1);

vm.prank(owner);
token.setBaseURI("");
assertEq(token.tokenURI(0), "");
}

function testTokenURIWithoutTrailingSlash() public {
vm.warp(publicStart + 1);
vm.deal(user, 1 ether);
vm.prank(user);
token.mintPublic{value: 0.01 ether}(user, 1);

vm.prank(owner);
token.setBaseURI("https://example.com/metadata");
assertEq(token.tokenURI(0), "https://example.com/metadata");
}

function testTokenURIForNonexistentTokenReverts() public {
vm.expectRevert();
token.tokenURI(9999);
}

function testContractNameAndVersion() public {
(string memory name, string memory version) = token.contractNameAndVersion();
// check that a value is returned
assert(bytes(name).length > 0);
assert(bytes(version).length > 0);
}
}
11 changes: 11 additions & 0 deletions test/erc721m/clones/ERC721MagicDropMetadataCloneable.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {console2} from "forge-std/console2.sol";

import {Ownable} from "solady/src/auth/Ownable.sol";
import {LibClone} from "solady/src/utils/LibClone.sol";
import {IERC721A} from "erc721a/contracts/IERC721A.sol";

import {ERC721MagicDropMetadataCloneable} from "contracts/nft/erc721m/clones/ERC721MagicDropMetadataCloneable.sol";
import {IERC721MagicDropMetadata} from "contracts/nft/erc721m/interfaces/IERC721MagicDropMetadata.sol";
Expand Down Expand Up @@ -214,6 +215,10 @@ contract ERC721MagicDropMetadataCloneableTest is Test {
assertTrue(token.supportsInterface(0x2a55205a));
// ERC4906 interfaceId = 0x49064906
assertTrue(token.supportsInterface(0x49064906));
// ERC721A interfaceId = 0x80ac58cd
assertTrue(token.supportsInterface(0x80ac58cd));
// ERC721Metadata interfaceId = 0x5b5e139f
assertTrue(token.supportsInterface(0x5b5e139f));
// Some random interface
assertFalse(token.supportsInterface(0x12345678));
}
Expand Down Expand Up @@ -242,4 +247,10 @@ contract ERC721MagicDropMetadataCloneableTest is Test {
token.setMaxSupply(10);
assertEq(token.maxSupply(), 10);
}

function testMaxSupplyCannotBeGreaterThan2ToThe64thPower() public {
vm.startPrank(owner);
vm.expectRevert(IMagicDropMetadata.MaxSupplyCannotBeGreaterThan2ToThe64thPower.selector);
token.setMaxSupply(2 ** 64);
}
}
37 changes: 37 additions & 0 deletions test/helpers/MerkleTestHelper.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

// Dummy merkle proof generation utilities for testing
contract MerkleTestHelper {
// This is a placeholder helper. In a real test, you'd generate a real merkle tree offline.
// Here we hardcode a single allowlisted address and its proof.
bytes32[] internal _proof;
bytes32 internal _root;
address internal _allowedAddr;

constructor(address allowedAddr) {
_allowedAddr = allowedAddr;
// For simplicity, root = keccak256(abi.encodePacked(_allowedAddr))
// Proof is empty since this is a single-leaf tree.
_root = keccak256(abi.encodePacked(_allowedAddr));
}

function getRoot() external view returns (bytes32) {
return _root;
}

function getProofFor(address addr) external view returns (bytes32[] memory) {
if (addr == _allowedAddr) {
// Single-leaf tree: no proof necessary except empty array
return new bytes32[](0);
} else {
// No valid proof
bytes32[] memory emptyProof;
return emptyProof;
}
}

function getAllowedAddress() external view returns (address) {
return _allowedAddr;
}
}

0 comments on commit fd3fc3b

Please sign in to comment.