# Ryan 第六周作業 合約 Gas Free優化 ### 只有簡單修改了一下白名單機制 ```solidity= pragma solidity >=0.7.0 <0.9.0; import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; contract CarMan is ERC721Enumerable, Ownable { using Strings for uint256; string public baseURI; string public baseExtension = ".json"; string public notRevealedUri; uint256 public cost = 0.5 ether; uint256 public maxSupply = 2000; uint256 public maxMintAmount = 10; uint256 public nftPerAddressLimit = 10; uint256 public currentPhaseMintMaxAmount = 110; uint32 public publicSaleStart = 1647136800; uint32 public preSaleStart = 1646964000; uint32 public vipSaleStart = 1646618400; bool public publicSalePaused = true; bool public preSalePaused = true; bool public vipSalePaused = true; bool public revealed = false; bool public onlyWhitelisted = true; //address[] whitelistedAddresses; bytes32 public root; //改用MerkleTree實作白名單機制 mapping(address => uint256) addressMintedBalance; mapping(address => uint256) vipMintAmount; // addresses to manage this contract mapping(address => bool) controllers; constructor( string memory _name, string memory _symbol, string memory _initBaseURI, string memory _initNotRevealedUri ) ERC721(_name, _symbol) { baseURI = _initBaseURI; notRevealedUri = _initNotRevealedUri; } // internal function _baseURI() internal view virtual override returns (string memory) { return baseURI; } // public function vipSaleMint(uint256 _mintAmount) public { require(_mintAmount > 0, "Mint Amount should be bigger than 0"); require( (!vipSalePaused) && (vipSaleStart <= block.timestamp), "Not Reach VIP Sale Time" ); uint256 supply = totalSupply(); require(_mintAmount > 0, "need to mint at least 1 NFT"); require( _mintAmount <= maxMintAmount, "max mint amount per session exceeded" ); require( supply + _mintAmount <= currentPhaseMintMaxAmount, "reach current Phase NFT limit" ); require(supply + _mintAmount <= maxSupply, "max NFT limit exceeded"); require(vipMintAmount[msg.sender] != 0, "user is not VIP"); uint256 ownerMintedCount = addressMintedBalance[msg.sender]; uint256 vipMintCount = vipMintAmount[msg.sender]; require( ownerMintedCount + _mintAmount <= vipMintCount, "max VIP Mint Amount exceeded" ); require( ownerMintedCount + _mintAmount <= nftPerAddressLimit, "max NFT per address exceeded" ); for (uint256 i = 1; i <= _mintAmount; i++) { addressMintedBalance[msg.sender]++; _safeMint(msg.sender, supply + i); } } function preSaleMint(uint256 _mintAmount, bytes32[] memory proof) public payable { require(_mintAmount > 0, "Mint Amount should be bigger than 0"); require( (!preSalePaused) && (preSaleStart <= block.timestamp), "Not Reach Pre Sale Time" ); uint256 supply = totalSupply(); require(_mintAmount > 0, "need to mint at least 1 NFT"); require( _mintAmount <= maxMintAmount, "max mint amount per session exceeded" ); require( supply + _mintAmount <= currentPhaseMintMaxAmount, "reach current Phase NFT limit" ); require(supply + _mintAmount <= maxSupply, "max NFT limit exceeded"); if (msg.sender != owner()) { if (onlyWhitelisted == true) { require(this.verify(proof), "user is not whitelisted"); uint256 ownerMintedCount = addressMintedBalance[msg.sender]; require( ownerMintedCount + _mintAmount <= nftPerAddressLimit, "max NFT per address exceeded" ); } require(msg.value >= cost * _mintAmount, "insufficient funds"); } for (uint256 i = 1; i <= _mintAmount; i++) { addressMintedBalance[msg.sender]++; _safeMint(msg.sender, supply + i); } } function publicSaleMint(uint256 _mintAmount, bytes32[] memory proof) public payable { require(_mintAmount > 0, "Mint Amount should be bigger than 0"); require( (!publicSalePaused) && (publicSaleStart <= block.timestamp), "Not Reach Public Sale Time" ); uint256 supply = totalSupply(); require(_mintAmount > 0, "need to mint at least 1 NFT"); require( _mintAmount <= maxMintAmount, "max mint amount per session exceeded" ); require( supply + _mintAmount <= currentPhaseMintMaxAmount, "reach current Phase NFT limit" ); require(supply + _mintAmount <= maxSupply, "max NFT limit exceeded"); if (msg.sender != owner()) { if (onlyWhitelisted == true) { require(this.verify(proof), "user is not whitelisted"); uint256 ownerMintedCount = addressMintedBalance[msg.sender]; require( ownerMintedCount + _mintAmount <= nftPerAddressLimit, "max NFT per address exceeded" ); } require(msg.value >= cost * _mintAmount, "insufficient funds"); } for (uint256 i = 1; i <= _mintAmount; i++) { addressMintedBalance[msg.sender]++; _safeMint(msg.sender, supply + i); } } /*function isWhitelisted(address _user) public view returns (bool) { for (uint i = 0; i < whitelistedAddresses.length; i++) { if (whitelistedAddresses[i] == _user) { return true; } } return false; }*/ //檢查白名單改用MerkleTree實作 function verify(bytes32[] memory proof) external view returns (bool) { return MerkleProof.verify( proof, root, keccak256(abi.encodePacked(msg.sender)) ); } function walletOfOwner(address _owner) public view returns (uint256[] memory) { uint256 ownerTokenCount = balanceOf(_owner); uint256[] memory tokenIds = new uint256[](ownerTokenCount); for (uint256 i; i < ownerTokenCount; i++) { tokenIds[i] = tokenOfOwnerByIndex(_owner, i); } return tokenIds; } function tokenURI(uint256 tokenId) public view virtual override returns (string memory) { require( _exists(tokenId), "ERC721Metadata: URI query for nonexistent token" ); if (revealed == false) { return notRevealedUri; } string memory currentBaseURI = _baseURI(); return bytes(currentBaseURI).length > 0 ? string( abi.encodePacked( currentBaseURI, tokenId.toString(), baseExtension ) ) : ""; } function publicSaleIsActive() public view returns (bool) { return ((publicSaleStart <= block.timestamp) && (!publicSalePaused)); } function preSaleIsActive() public view returns (bool) { return ((preSaleStart <= block.timestamp) && (!preSalePaused)); } function vipSaleIsActive() public view returns (bool) { return ((vipSaleStart <= block.timestamp) && (!vipSalePaused)); } function checkVIPMintAmount(address _account) public view returns (uint256) { return vipMintAmount[_account]; } // for controller function reveal(bool _state) public { require( controllers[msg.sender], "Only controllers can operate this function" ); revealed = _state; } function setNftPerAddressLimit(uint256 _limit) public { require( controllers[msg.sender], "Only controllers can operate this function" ); nftPerAddressLimit = _limit; } function setCost(uint256 _newCost) public { require( controllers[msg.sender], "Only controllers can operate this function" ); cost = _newCost; } function setmaxMintAmount(uint256 _newmaxMintAmount) public { require( controllers[msg.sender], "Only controllers can operate this function" ); maxMintAmount = _newmaxMintAmount; } function setcurrentPhaseMintMaxAmount(uint256 _newPhaseAmount) public { require( controllers[msg.sender], "Only controllers can operate this function" ); currentPhaseMintMaxAmount = _newPhaseAmount; } function setPublicSaleStart(uint32 timestamp) public { require( controllers[msg.sender], "Only controllers can operate this function" ); publicSaleStart = timestamp; } function setPreSaleStart(uint32 timestamp) public { require( controllers[msg.sender], "Only controllers can operate this function" ); preSaleStart = timestamp; } function setVIPSaleStart(uint32 timestamp) public { require( controllers[msg.sender], "Only controllers can operate this function" ); vipSaleStart = timestamp; } function setBaseURI(string memory _newBaseURI) public { require( controllers[msg.sender], "Only controllers can operate this function" ); baseURI = _newBaseURI; } function setBaseExtension(string memory _newBaseExtension) public { require( controllers[msg.sender], "Only controllers can operate this function" ); baseExtension = _newBaseExtension; } function setNotRevealedURI(string memory _notRevealedURI) public { require( controllers[msg.sender], "Only controllers can operate this function" ); notRevealedUri = _notRevealedURI; } function setPreSalePause(bool _state) public { require( controllers[msg.sender], "Only controllers can operate this function" ); preSalePaused = _state; } function setVIPSalePause(bool _state) public { require( controllers[msg.sender], "Only controllers can operate this function" ); vipSalePaused = _state; } function setVIPMintAmount( address[] memory _accounts, uint256[] memory _amounts ) public { require( controllers[msg.sender], "Only controllers can operate this function" ); require( _accounts.length == _amounts.length, "accounts and amounts array length mismatch" ); for (uint256 i = 0; i < _accounts.length; ++i) { vipMintAmount[_accounts[i]] = _amounts[i]; } } function setPublicSalePause(bool _state) public { require( controllers[msg.sender], "Only controllers can operate this function" ); publicSalePaused = _state; } function setOnlyWhitelisted(bool _state) public { require( controllers[msg.sender], "Only controllers can operate this function" ); onlyWhitelisted = _state; } /*function whitelistUsers(address[] calldata _users) public { require( controllers[msg.sender], "Only controllers can operate this function" ); delete whitelistedAddresses; whitelistedAddresses = _users; }*/ //改用MerkleTree 設定白名單root function setRoot(bytes32 _root) external { require(msg.sender == owner(), "Only owner can set root"); root = _root; } //only owner /** * enables an address for management * @param controller the address to enable */ function addController(address controller) external onlyOwner { controllers[controller] = true; } /** * disables an address for management * @param controller the address to disbale */ function removeController(address controller) external onlyOwner { controllers[controller] = false; } function withdraw() public onlyOwner { (bool success, ) = payable(msg.sender).call{ value: address(this).balance }(""); require(success); } } ``` ###### tags: `Solidity 工程師實戰營第 5 期`