Are NFT projects doing starting index randomization and provenance wrong or is it just me?

@PatrickAlphaC yeah layer 2s are nice and cool, love polygon and optimism, but its to complicated for the average joe, there has been projects who minted on L2 and as I recall it did not go so well, everything happens directly on ETH, nobody wants to bridge their stuff. For the average joe metamask UX is already not grea, but telling my art friends they gotta bridge something because the old eth system is to expensive, not really 2021 user experience you want to have.

Anyway, back to the point. Yeah I would need a random number on mint like following code:

function _mint(address _to) internal returns (uint) {
        uint id = randomIndex();
        numTokens = numTokens + 1;
        _mint(_to, id);
    }

 function randomIndex() internal returns (uint) {
        uint totalSize = TOKEN_LIMIT - numTokens;
        uint index = uint(keccak256(abi.encodePacked(nonce, msg.sender, block.difficulty, block.timestamp))) % totalSize;
        uint value = 0;
        if (indices[index] != 0) {
            value = indices[index];
        } else {
            value = index;
        }

        if (indices[totalSize - 1] == 0) {
      
            indices[index] = totalSize - 1;
        } else {
            indices[index] = indices[totalSize - 1];
        }
        nonce++;
        return value.add(1);
    }

Except that this randomIndex function right here could be snooped up, user than could cancel their transaction if they dont like what they got. In order to this with Chainlink it would need be called on the mint, which means it needs to be called every time right?

Ah I see, glad you recognize this could be snooped!

So yes, if you want 1 mint == 1 NFT, then yes, you'd have to call Chainlink VRF each time.

However, if you wanted 1 mint == 10,000 NFTs, then you'd only need to call the Chainlink VRF once, and each would still be random and un-snoopable.

In both of these cases, after the original transaction finishes, you can no longer use the expand randomness ability.

Does this make sense?

@PatrickAlphaC Completely patrick, but unfortunately for my use case I can not use chain link, because people will mint themselfs, thats leaves me with doing the offset thingy.

@mhbln interested to hear how you avoid the snooping issue then? Obviously it requires a certain technical talent/willingness to spend gas to cheat, but we've seen from Meebits that exists. So is the best solution to go with the known gameable solution?

@PatrickAlphaC i know this is the problem that Chainlink aims to solve, but with a 2LINK/call fee, that may squeeze some members of the community out from using it. Any thoughts?

@swixx it depends. If you have a cheap drop I highly doubt ppl will spend so much gas.
Well, all the solutions kinda suck at this point. The smartest and most "decentral" way to do it is probably the idea proposed here. You can upload the Metadata before and then set the offset on the end. You can do that either a simple block.number solution or you could use chain link, though I think you don't need that. Because at that point everything is already Minted.

Let's face it. People actually don't give two fucks about it. You should look at all these contracts. Pudgy penguins, cool cats, they point to meta data on their own server. They could just take down the server and your nft is gone. Or they could simply replace it with another. It ridiculous. I think in this field mostly ppl think they care but actually don't or have no idea what they invest in. So dont over think it I would say.

You should code your project whichever you deem best. However, understanding the risks is really important. If do deploy as you say, it will either:

  1. Cap how big your project can be
  2. Give hackers an easy exploit angle

If you gain any value on your project, you're setting yourself up to be hacked.

2 LINK / call is to cover the cost of true randomness, it costs gas to the Chainlink node operators to verify the randomness, and this is only for mainnet Ethereum. However, the added security of your protocol with true randomness gives a level of validity to your project. It's the difference between randomness being worthless and meaningful. I've seen a number of projects be concerned about the added cost, but then realize the validity of true randomness makes the project truly valuable and gone forward to see great success.

There is a reason that projects like Axie Infinity, Ethercards, Aavegotchi, and many other successful NFT projects that need randomness use Chainlink VRF.

2 Likes

I get it: Chainlink is a great product.
If I would do a serious game like axie, I would use it too, but this generative avatar projects were the randomness would be only necessary for the mint its too much overhead, they usually sell for 0,04 eth. Nobody wants to spend 10.000 x 2 LINK for that if its only necessary for the mint, also no hacking will happen after that, the approach described here is completely fine for the given use case. And yes for another it probably wouldn't be enough and you should rather use Chainlink.

2 Likes

What i don't like about the OpenZeppelin base implementations is the idea of making the baseURI editable. I think common practice should be to deploy the contract WITH the IPFS CID as the provenance hash and have it unchangeable.

This way, devs can't move around metadata files after sale is complete.

Note, i don't mean necessarily publishing / pinning the metadata before launch, but publishing the eventual hash of it before launch.

I think this solves the biggest attack vector / scam opportunity with current NFT practices.

I built a little extension that handles this here: https://github.com/1001-digital/erc721-extensions/blob/main/contracts/WithIPFSMetaData.sol

That paired with semi random assignment (sorry @PatrickAlphaC :sweat_smile:) should be enough for 99% of NFT projects to make them SCAM resistant (also by the dev team). Simple (semi) random assignment extension here: https://github.com/1001-digital/erc721-extensions/blob/main/contracts/RandomlyAssigned.sol

1 Like

This is not true if you just don't publish metadata before end of sale.

Good info Patrick - I've actually didn't work with Chainlink yet but it's next on my learning list.

1 Like

@mhbln sounds like you understand the tradeoffs. We have a high-security mindset when it comes to these projects, and I just want to make sure you understand the risks if you go one of these non-random routes.

@jalil
Yeah of course. But the coolest thing would actually to have the Metadata beforehand without being able to Crack it, which would be possible if you do true randomness at mint (which is too expensive).
See that's what I don't like with the current state of affairs in the eth world. You either gotta go on a layer 2 and let your customers bridge their funds (which 99% of average Joe's don't understand) or you have to pay tons of money (e.g chain link for every call at mint) to run your code the best way. I mean what year is it? Its just a lot of nonsense you gotta do to fit an overloaded old system.
In what real world would a customer pay 5 dollars minimum transaction fee to buy a t-shirt or pay 40 dollars to get the true random lottery numbers. Ridiculous.

Anyway, sorry for my little rant. It is what it is.
Back to the point. With what is described here in the forum all the way scrolled up you could actually post meta data beforehand and it wouldn't matter because you set the offset on the end.

@jalil This is similar to the solution I proposed.
In my upcoming NFT, the contract constructor takes 8 args:

                string memory name,
                string memory symbol,
                uint256 maxNftSupply,
                uint256 revealTs,
                StorageType main,
                string memory provenance,
                string memory ipfsBase,
                string memory arweaveBase

Where StorageType is an enum and the main is the selected type - I'm doing both IPFS and Arweave and therefore need to determine which base URI to use in tokenUri by default.

The provenance hash is the sha-256 of sha-256's - I think it's preferable to stick to something that is platform-agnostic.

I am also keeping a setIPFSBase and setArweaveBase just in case.
To increase trust I also employ a locked state variable which once set cannot be unset and disables tampering with the baseUris (modifiers are handy here).

Whether we'll be launching with the baseUris (therefore making the collection visible) is still a question but mainly one of preference rather than exploitability.

Also do take note that the "seed" for determining startingIndex can also be derived from more factors other than block.number, such as block.difficulty etc...

@mhbln Just popped into my head tell me if I'm right,

You could have all the metadata available beforehand and then use Chainlink VRF only inside setStartingIndex to get a true random number for your startingIndex.

You could even further restrict access to that function to onlyOwner.

@PatrickAlphaC forgive my ignorance but is there a local testing simulator for Chainlink that developers can use for unit tests?

2 Likes

@guyo13 yeah sure, thats a way to do it

You sure can!

This has unit tests written for the Chainlink VRF in hardhat. But you can get them for brownie and truffle as well.

@jalil What you mean by publishing it? I can just look up when you deployed the contract, find the ipfs-id. look up the metadata for all nfts, determine which is rare and which isn't and than mint there, with the semi-random thing I can just cancel out the transaction in the mempool if I do not like what randomIndex I got, its just gas intense.

Also making the baseUri non editable is a nice idea in theory, but what if your ipfs node doesnt get pinned at one point anymore and boom your data is gone or technology changes in the next years, you might want to change the baseUri.

@guyo13 the approach with the offset just has the one problem that you can not really give it a name such as "Cool Cat #123" because people will be confused since they will own tokenId #0 and metadata #123 > so on pages like rarible it will just show up as "untitled", like the bored apes do.

Yeah that solution is cool, but it messes up the beauty of matching tokenID to metadataID, no?

I am also keeping a setIPFSBase and setArweaveBase just in case.

Reasonable but i personally prefer no admin abilities at all...

What you mean by publishing it?

You can publish the smart contract including the CID without publishing the CID on IPFS. You create the hash locally before pinning it, so yes everyone will see the hash, but no one will know its' content until you publish it.

Also making the baseUri non editable is a nice idea in theory, but what if your ipfs node doesnt get pinned at one point anymore and boom your data is gone or technology changes in the next years, you might want to change the baseUri.

Here you are implying that you still have the metadata files and media assets lying around somewhere? In that case, you can just pin them again - the hash will be the same.
I think it's important/reasonable to regard the responsibility of pinning as the holders' responsibility, not the project creators responsibility. Same with the cryptopunks. The hash is in the contract, every owner has the responsibility to keep a copy of the punks.png to be able to verify the hash.

Yeah that solution is cool, but it messes up the beauty of matching tokenID to metadataID, no?

It would mess up the tokenID to metadataID matching. You could do it this way and then have setBaseUri option still in it, with an option to block it. That way the community could vote after if they want the metaData reuploaded so its matching correctly and then you block yourself from doing that again.

1 Like