---
# System prepended metadata

title: Ryan 第六周作業 合約 Gas Free優化
tags: [Solidity 工程師實戰營第 5 期]

---

# 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 期`