CryptoPunks v1 vs CryptoPhunks
/**
// SPDX-License-Identifier: UNLICENSE
 *Submitted for verification at Etherscan.io on 2017-06-17
pragma solidity ^0.8.0;
*/
pragma solidity ^0.4.8;
import "@openzeppelin/contracts/access/Ownable.sol";
contract CryptoPunks {
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
    // You can use this hash to verify the image file containing all the punks
contract CryptoPhunksV2 is Ownable, ERC721Enumerable, ReentrancyGuard {
    string public imageHash = "ac39af4793119ee46bbff351d8cb6b5f23da60222126add4268e261199a2921b";
  using Counters for Counters.Counter;
  using Strings for uint256;
    address owner;
  // You can use this hash to verify the image file containing all the phunks
  string public constant imageHash =
    "122dab9670c21ad538dafdbb87191c4d7114c389af616c42c54556aa2211b899";
    string public standard = 'CryptoPunks';
  constructor() ERC721("CryptoPhunksV2", "PHUNK") {}
    string public name;
    string public symbol;
    uint8 public decimals;
    uint256 public totalSupply;
    uint public nextPunkIndexToAssign = 0;
  bool public isSaleOn = false;
    //bool public allPunksAssigned = false;
  bool public saleHasBeenStarted = false;
    uint public punksRemainingToAssign = 0;
    uint public numberOfPunksToReserve;
    uint public numberOfPunksReserved = 0;
    //mapping (address => uint) public addressToPunkIndex;
  uint256 public constant MAX_MINTABLE_AT_ONCE = 50;
    mapping (uint => address) public punkIndexToAddress;
    /* This creates an array with all balances */
  uint256[10000] private _availableTokens;
    mapping (address => uint256) public balanceOf;
  uint256 private _numAvailableTokens = 10000;
  uint256 private _numFreeRollsGiven = 0;
    struct Offer {
  mapping(address => uint256) public freeRollPhunks;
        bool isForSale;
        uint punkIndex;
        address seller;
        uint minValue;          // in ether
        address onlySellTo;     // specify to sell only to a specific person
    }
    // A record of punks that are offered for sale at a specific minimum value, and perhaps to a specific person
  uint256 private _lastTokenIdMintedInInitialSet = 10000;
    mapping (uint => Offer) public punksOfferedForSale;
    mapping (address => uint) public pendingWithdrawals;
  function numTotalPhunks() public view virtual returns (uint256) {
    return 10000;
  }
    event Assign(address indexed to, uint256 punkIndex);
  function freeRollMint() public nonReentrant() {
    event Transfer(address indexed from, address indexed to, uint256 value);
    uint256 toMint = freeRollPhunks[msg.sender];
    event PunkTransfer(address indexed from, address indexed to, uint256 punkIndex);
    freeRollPhunks[msg.sender] = 0;
    event PunkOffered(uint indexed punkIndex, uint minValue, address indexed toAddress);
    uint256 remaining = numTotalPhunks() - totalSupply();
    event PunkBought(uint indexed punkIndex, uint value, address indexed fromAddress, address indexed toAddress);
    if (toMint > remaining) {
    event PunkNoLongerForSale(uint indexed punkIndex);
      toMint = remaining;
    }
    _mint(toMint);
  }
    /* Initializes contract with initial supply tokens to the creator of the contract */
  function getNumFreeRollPhunks(address owner) public view returns (uint256) {
    function CryptoPunks() payable {
    return freeRollPhunks[owner];
        //        balanceOf[msg.sender] = initialSupply;              // Give the creator all initial tokens
  }
        owner = msg.sender;
        totalSupply = 10000;                        // Update total supply
  function mint(uint256 _numToMint) public payable nonReentrant() {
        punksRemainingToAssign = totalSupply;
    require(isSaleOn, "Sale hasn't started.");
        numberOfPunksToReserve = 1000;
    uint256 totalSupply = totalSupply();
        name = "CRYPTOPUNKS";                                   // Set the name for display purposes
    require(
        symbol = "Ͼ";                               // Set the symbol for display purposes
      totalSupply + _numToMint <= numTotalPhunks(),
        decimals = 0;                                       // Amount of decimals for display purposes
      "There aren't this many phunks left."
    );
    uint256 costForMintingPhunks = getCostForMintingPhunks(_numToMint);
    require(
      msg.value >= costForMintingPhunks,
      "Too little sent, please send more eth."
    );
    if (msg.value > costForMintingPhunks) {
      payable(msg.sender).transfer(msg.value - costForMintingPhunks);
    }
    }
    function reservePunksForOwner(uint maxForThisRun) {
    _mint(_numToMint);
        if (msg.sender != owner) throw;
  }
        if (numberOfPunksReserved >= numberOfPunksToReserve) throw;
        uint numberPunksReservedThisRun = 0;
  // internal minting function
        while (numberOfPunksReserved < numberOfPunksToReserve && numberPunksReservedThisRun < maxForThisRun) {
  function _mint(uint256 _numToMint) internal {
            punkIndexToAddress[nextPunkIndexToAssign] = msg.sender;
    require(_numToMint <= MAX_MINTABLE_AT_ONCE, "Minting too many at once.");
            Assign(msg.sender, nextPunkIndexToAssign);
            numberPunksReservedThisRun++;
    uint256 updatedNumAvailableTokens = _numAvailableTokens;
            nextPunkIndexToAssign++;
    for (uint256 i = 0; i < _numToMint; i++) {
        }
      uint256 newTokenId = useRandomAvailableToken(_numToMint, i);
        punksRemainingToAssign -= numberPunksReservedThisRun;
      _safeMint(msg.sender, newTokenId);
        numberOfPunksReserved += numberPunksReservedThisRun;
      updatedNumAvailableTokens--;
        balanceOf[msg.sender] += numberPunksReservedThisRun;
    }
    }
    _numAvailableTokens = updatedNumAvailableTokens;
  }
    function getPunk(uint punkIndex) {
  function useRandomAvailableToken(uint256 _numToFetch, uint256 _i)
        if (punksRemainingToAssign == 0) throw;
    internal
        if (punkIndexToAddress[punkIndex] != 0x0) throw;
    returns (uint256)
        punkIndexToAddress[punkIndex] = msg.sender;
  {
        balanceOf[msg.sender]++;
    uint256 randomNum =
        punksRemainingToAssign--;
      uint256(
        Assign(msg.sender, punkIndex);
        keccak256(
          abi.encode(
            msg.sender,
            tx.gasprice,
            block.number,
            block.timestamp,
            blockhash(block.number - 1),
            _numToFetch,
            _i
          )
        )
      );
    uint256 randomIndex = randomNum % _numAvailableTokens;
    return useAvailableTokenAtIndex(randomIndex);
  }
  function useAvailableTokenAtIndex(uint256 indexToUse)
    internal
    returns (uint256)
  {
    uint256 valAtIndex = _availableTokens[indexToUse];
    uint256 result;
    if (valAtIndex == 0) {
      // This means the index itself is still an available token
      result = indexToUse;
    } else {
      // This means the index itself is not an available token, but the val at that index is.
      result = valAtIndex;
    }
    }
    // Transfer ownership of a punk to another user without requiring payment
    uint256 lastIndex = _numAvailableTokens - 1;
    function transferPunk(address to, uint punkIndex) {
    if (indexToUse != lastIndex) {
        if (punkIndexToAddress[punkIndex] != msg.sender) throw;
      // Replace the value at indexToUse, now that it's been used.
        punkIndexToAddress[punkIndex] = to;
      // Replace it with the data from the last index in the array, since we are going to decrease the array size afterwards.
        balanceOf[msg.sender]--;
      uint256 lastValInArray = _availableTokens[lastIndex];
        balanceOf[to]++;
      if (lastValInArray == 0) {
        Transfer(msg.sender, to, 1);
        // This means the index itself is still an available token
        PunkTransfer(msg.sender, to, punkIndex);
        _availableTokens[indexToUse] = lastIndex;
      } else {
        // This means the index itself is not an available token, but the val at that index is.
        _availableTokens[indexToUse] = lastValInArray;
      }
    }
    }
    function punkNoLongerForSale(uint punkIndex) {
    _numAvailableTokens--;
        if (punkIndexToAddress[punkIndex] != msg.sender) throw;
    return result;
        punksOfferedForSale[punkIndex] = Offer(false, punkIndex, msg.sender, 0, 0x0);
  }
        PunkNoLongerForSale(punkIndex);
  function getCostForMintingPhunks(uint256 _numToMint)
    public
    view
    returns (uint256)
  {
    require(
      totalSupply() + _numToMint <= numTotalPhunks(),
      "There aren't this many phunks left."
    );
    if (_numToMint == 1) {
      return 0.02 ether;
    } else if (_numToMint == 3) {
      return 0.05 ether;
    } else if (_numToMint == 5) {
      return 0.07 ether;
    } else if (_numToMint == 10) {
      return 0.10 ether;
    } else {
      revert("Unsupported mint amount");
    }
    }
  }
    function offerPunkForSale(uint punkIndex, uint minSalePriceInWei) {
  function getPhunksBelongingToOwner(address _owner)
        if (punkIndexToAddress[punkIndex] != msg.sender) throw;
    external
        punksOfferedForSale[punkIndex] = Offer(true, punkIndex, msg.sender, minSalePriceInWei, 0x0);
    view
        PunkOffered(punkIndex, minSalePriceInWei, 0x0);
    returns (uint256[] memory)
  {
    uint256 numPhunks = balanceOf(_owner);
    if (numPhunks == 0) {
      return new uint256[](0);
    } else {
      uint256[] memory result = new uint256[](numPhunks);
      for (uint256 i = 0; i < numPhunks; i++) {
        result[i] = tokenOfOwnerByIndex(_owner, i);
      }
      return result;
    }
    }
  }
    function offerPunkForSaleToAddress(uint punkIndex, uint minSalePriceInWei, address toAddress) {
  /*
        if (punkIndexToAddress[punkIndex] != msg.sender) throw;
   * Dev stuff.
        punksOfferedForSale[punkIndex] = Offer(true, punkIndex, msg.sender, minSalePriceInWei, toAddress);
   */
        PunkOffered(punkIndex, minSalePriceInWei, toAddress);
  // metadata URI
  string private _baseTokenURI;
  function _baseURI() internal view virtual override returns (string memory) {
    return _baseTokenURI;
  }
  function tokenURI(uint256 _tokenId)
    public
    view
    override
    returns (string memory)
  {
    string memory base = _baseURI();
    string memory _tokenURI = Strings.toString(_tokenId);
    // If there is no base URI, return the token URI.
    if (bytes(base).length == 0) {
      return _tokenURI;
    }
    }
    function buyPunk(uint punkIndex) payable {
    return string(abi.encodePacked(base, _tokenURI));
        Offer offer = punksOfferedForSale[punkIndex];
  }
        if (!offer.isForSale) throw;                // punk not actually for sale
        if (offer.onlySellTo != 0x0 && offer.onlySellTo != msg.sender) throw;  // punk not supposed to be sold to this user
        if (msg.value < offer.minValue) throw;      // Didn't send enough ETH
        if (offer.seller != punkIndexToAddress[punkIndex]) throw; // Seller no longer owner of punk
        punkIndexToAddress[punkIndex] = msg.sender;
  // contract metadata URI for opensea
        balanceOf[offer.seller]--;
  string public contractURI;
        balanceOf[msg.sender]++;
        Transfer(offer.seller, msg.sender, 1);
        punkNoLongerForSale(punkIndex);
  /*
        pendingWithdrawals[offer.seller] += msg.value;
   * Owner stuff
        PunkBought(punkIndex, msg.value, offer.seller, msg.sender);
   */
  function startSale() public onlyOwner {
    isSaleOn = true;
    saleHasBeenStarted = true;
  }
  function endSale() public onlyOwner {
    isSaleOn = false;
  }
  function giveFreeRoll(address receiver) public onlyOwner {
    // max number of free mints we can give to the community for promotions/marketing
    require(_numFreeRollsGiven < 200, "already given max number of free rolls");
    uint256 freeRolls = freeRollPhunks[receiver];
    freeRollPhunks[receiver] = freeRolls + 1;
    _numFreeRollsGiven = _numFreeRollsGiven + 1;
  }
  // for handing out free rolls to v1 phunk owners
  // details on seeding info here: https://gist.github.com/cryptophunks/7f542feaee510e12464da3bb2a922713
  function seedFreeRolls(
    address[] memory tokenOwners,
    uint256[] memory numOfFreeRolls
  ) public onlyOwner {
    require(
      !saleHasBeenStarted,
      "cannot seed free rolls after sale has started"
    );
    require(
      tokenOwners.length == numOfFreeRolls.length,
      "tokenOwners does not match numOfFreeRolls length"
    );
    // light check to make sure the proper values are being passed
    require(numOfFreeRolls[0] <= 3, "cannot give more than 3 free rolls");
    for (uint256 i = 0; i < tokenOwners.length; i++) {
      freeRollPhunks[tokenOwners[i]] = numOfFreeRolls[i];
    }
    }
  }
    function withdraw() {
  // for seeding the v2 contract with v1 state
        uint amount = pendingWithdrawals[msg.sender];
  // details on seeding info here: https://gist.github.com/cryptophunks/7f542feaee510e12464da3bb2a922713
        // Remember to zero the pending refund before
  function seedInitialContractState(
        // sending to prevent re-entrancy attacks
    address[] memory tokenOwners,
        pendingWithdrawals[msg.sender] = 0;
    uint256[] memory tokens
        msg.sender.transfer(amount);
  ) public onlyOwner {
    require(
      !saleHasBeenStarted,
      "cannot initial phunk mint if sale has started"
    );
    require(
      tokenOwners.length == tokens.length,
      "tokenOwners does not match tokens length"
    );
    uint256 lastTokenIdMintedInInitialSetCopy = _lastTokenIdMintedInInitialSet;
    for (uint256 i = 0; i < tokenOwners.length; i++) {
      uint256 token = tokens[i];
      require(
        lastTokenIdMintedInInitialSetCopy > token,
        "initial phunk mints must be in decreasing order for our availableToken index to work"
      );
      lastTokenIdMintedInInitialSetCopy = token;
      useAvailableTokenAtIndex(token);
      _safeMint(tokenOwners[i], token);
    }
    }
    _lastTokenIdMintedInInitialSet = lastTokenIdMintedInInitialSetCopy;
  }
  // URIs
  function setBaseURI(string memory baseURI) external onlyOwner {
    _baseTokenURI = baseURI;
  }
  function setContractURI(string memory _contractURI) external onlyOwner {
    contractURI = _contractURI;
  }
  function withdrawMoney() public payable onlyOwner {
    (bool success, ) = msg.sender.call{value: address(this).balance}("");
    require(success, "Transfer failed.");
  }
  function _beforeTokenTransfer(
    address from,
    address to,
    uint256 tokenId
  ) internal virtual override(ERC721Enumerable) {
    super._beforeTokenTransfer(from, to, tokenId);
  }
  function supportsInterface(bytes4 interfaceId)
    public
    view
    virtual
    override(ERC721Enumerable)
    returns (bool)
  {
    return super.supportsInterface(interfaceId);
  }
}
}