CryptoPunks v1 vs CryptoPhunks
126 removals
140 lines
311 additions
314 lines
/**
// 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);
}
}
}