Skip to content

Commit

Permalink
feat: AllowlistMintDN404 (Vectorized#37)
Browse files Browse the repository at this point in the history
* feat: SimpleAllowlistDN404

* chore: live

* Rename and add total supply

---------

Co-authored-by: pop-punk <[email protected]>
Co-authored-by: cygaar <[email protected]>
  • Loading branch information
3 people authored Feb 12, 2024
1 parent 30f4cee commit ba193e4
Show file tree
Hide file tree
Showing 5 changed files with 231 additions and 1 deletion.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@
[submodule "lib/forge-std"]
path = lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "lib/murky"]
path = lib/murky
url = https://github.com/dmfxyz/murky
2 changes: 1 addition & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ evm_version = "paris" # Shanghai will be tested in the CI.
auto_detect_solc = false
optimizer = true
optimizer_runs = 1_000
gas_limit = 100_000_000 # ETH is 30M, but we use a higher value.
gas_limit = 100_000_000_000 # ETH is 30M, but we use a higher value.
remappings = [
"forge-std=test/utils/forge-std/"
]
Expand Down
1 change: 1 addition & 0 deletions lib/murky
Submodule murky added at 40de6e
135 changes: 135 additions & 0 deletions src/example/AllowlistMintDN404.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// SPX-License-Identifier: MIT
pragma solidity ^0.8.4;

import "../DN404.sol";
import "../DN404Mirror.sol";
import {Ownable} from "solady/auth/Ownable.sol";
import {LibString} from "solady/utils/LibString.sol";
import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol";
import {MerkleProofLib} from "solady/utils/MerkleProofLib.sol";

contract AllowlistMintDN404 is DN404, Ownable {
string private _name;
string private _symbol;
string private _baseURI;
bytes32 private allowlistRoot;
uint120 public publicPrice;
uint120 public allowlistPrice;
bool public live;
uint256 public maxMint;
uint256 public numMinted;

uint256 public constant MAX_SUPPLY = 5000;

mapping(address => bool) public minted;

error InvalidProof();
error InvalidMint();
error InvalidPrice();
error ExceedsMaxMint();
error TotalSupplyReached();
error NotLive();

modifier isValidMint(uint256 price, uint256 amount) {
if (!live) {
revert NotLive();
}
if (price * amount != msg.value) {
revert InvalidPrice();
}
if (amount > maxMint) {
revert ExceedsMaxMint();
}
if (numMinted + amount > MAX_SUPPLY) {
revert TotalSupplyReached();
}
_;
}

constructor(
string memory name_,
string memory symbol_,
bytes32 allowlistRoot_,
uint256 maxMint_,
uint120 publicPrice_,
uint120 allowlistPrice_,
uint96 initialTokenSupply,
address initialSupplyOwner
) {
_initializeOwner(msg.sender);

_name = name_;
_symbol = symbol_;
allowlistRoot = allowlistRoot_;
maxMint = maxMint_;
publicPrice = publicPrice_;
allowlistPrice = allowlistPrice_;

address mirror = address(new DN404Mirror(msg.sender));
_initializeDN404(initialTokenSupply, initialSupplyOwner, mirror);
}

function mint(uint256 amount) public payable isValidMint(publicPrice, amount) {
if (minted[msg.sender]) revert InvalidMint();
minted[msg.sender] = true;
unchecked {
++numMinted;
}
_mint(msg.sender, amount);
}

function allowlistMint(uint256 amount, bytes32[] calldata proof)
public
payable
isValidMint(allowlistPrice, amount)
{
if (
!MerkleProofLib.verifyCalldata(
proof, allowlistRoot, keccak256(abi.encodePacked(msg.sender))
)
) {
revert InvalidProof();
}
if (minted[msg.sender]) revert InvalidMint();
minted[msg.sender] = true;
unchecked {
++numMinted;
}
_mint(msg.sender, amount);
}

function setBaseURI(string calldata baseURI_) public onlyOwner {
_baseURI = baseURI_;
}

function setPrices(uint120 publicPrice_, uint120 allowlistPrice_) public onlyOwner {
publicPrice = publicPrice_;
allowlistPrice = allowlistPrice_;
}

function setMaxMint(uint256 maxMint_) public onlyOwner {
maxMint = maxMint_;
}

function toggleLive() public onlyOwner {
live = !live;
}

function withdraw() public onlyOwner {
SafeTransferLib.safeTransferAllETH(msg.sender);
}

function name() public view override returns (string memory) {
return _name;
}

function symbol() public view override returns (string memory) {
return _symbol;
}

function tokenURI(uint256 tokenId) public view override returns (string memory result) {
if (bytes(_baseURI).length != 0) {
result = string(abi.encodePacked(_baseURI, LibString.toString(tokenId)));
}
}
}
91 changes: 91 additions & 0 deletions test/SimpleAllowlistDN404.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import "./utils/SoladyTest.sol";
import {AllowlistMintDN404} from "../src/example/AllowlistMintDN404.sol";
import {Merkle} from "murky/src/Merkle.sol";

contract AllowlistMintDN404Test is SoladyTest {
AllowlistMintDN404 dn;
Merkle allowlistMerkle;

address alice = address(111);
address bob = address(222);

bytes32 allowlistRoot;
bytes32[] allowlistData = new bytes32[](2);

uint120 publicPrice = 0.02 ether;
uint120 allowlistPrice = 0.01 ether;

function setUp() public {
allowlistMerkle = new Merkle();
allowlistData[0] = bytes32(keccak256(abi.encodePacked(alice)));
allowlistRoot = allowlistMerkle.getRoot(allowlistData);

dn = new AllowlistMintDN404(
"DN404", "DN", allowlistRoot, 10, publicPrice, allowlistPrice, 1000, address(this)
);
dn.toggleLive();
payable(bob).transfer(10 ether);
payable(alice).transfer(10 ether);
}

function testMint() public {
vm.startPrank(bob);

vm.expectRevert(AllowlistMintDN404.InvalidPrice.selector);
dn.mint{value: 1 ether}(1);

vm.expectRevert(AllowlistMintDN404.ExceedsMaxMint.selector);
dn.mint{value: 11 * publicPrice}(11);

dn.mint{value: 5 * publicPrice}(5);
assertEq(dn.totalSupply(), 1005);
assertEq(dn.balanceOf(bob), 5);

vm.expectRevert(AllowlistMintDN404.InvalidMint.selector);
dn.mint{value: 6 * publicPrice}(6);

vm.stopPrank();
}

function testTotalSupplyReached() public {
// Mint out whole supply
for (uint160 i; i < 5000; ++i) {
address a = address(i + 1000);
payable(a).transfer(1 ether);
vm.prank(a);
dn.mint{value: publicPrice}(1);
}

vm.prank(alice);
vm.expectRevert(AllowlistMintDN404.TotalSupplyReached.selector);
dn.mint{value: publicPrice}(1);
}

function testAllowlistMint() public {
vm.prank(bob);

bytes32[] memory proof = allowlistMerkle.getProof(allowlistData, 0);
vm.expectRevert(AllowlistMintDN404.InvalidProof.selector);
dn.allowlistMint{value: 5 * allowlistPrice}(5, proof);

vm.startPrank(alice);

vm.expectRevert(AllowlistMintDN404.InvalidPrice.selector);
dn.allowlistMint{value: 1 ether}(1, proof);

vm.expectRevert(AllowlistMintDN404.ExceedsMaxMint.selector);
dn.allowlistMint{value: 11 * allowlistPrice}(11, proof);

dn.allowlistMint{value: 5 * allowlistPrice}(5, proof);
assertEq(dn.totalSupply(), 1005);
assertEq(dn.balanceOf(alice), 5);

vm.expectRevert(AllowlistMintDN404.InvalidMint.selector);
dn.allowlistMint{value: 6 * allowlistPrice}(6, proof);

vm.stopPrank();
}
}

0 comments on commit ba193e4

Please sign in to comment.